Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 1657: enrich document with inline styles and js #1672

Merged
merged 5 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "JDN",
"description": "JDN – helps Test Automation Engineer to create Page Objects in the test automation framework and speed up test development",
"devtools_page": "index.html",
"version": "3.15.8",
"version": "3.15.9",
"icons": {
"128": "icon128.png"
},
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jdn-ai-chrome-extension",
"version": "3.15.8",
"version": "3.15.9",
"description": "jdn-ai chrome extension",
"scripts": {
"start": "webpack --watch --env devenv",
Expand Down
62 changes: 62 additions & 0 deletions src/common/utils/getFullDocumentWithStyles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import connector from '../../pageServices/connector';

async function waitForAllStylesToLoad() {
const styleSheets = document.querySelectorAll('link[rel="stylesheet"]');

const loadPromises = Array.from(styleSheets).map(
(sheet) =>
new Promise<void>((resolve) => {
const linkElement = sheet as HTMLLinkElement;
if (linkElement.sheet) {
resolve();
} else {
linkElement.onload = () => resolve();
linkElement.onerror = () => resolve();
}
}),
);

await Promise.all(loadPromises);
}

export const getFullDocumentWithStyles = async () => {
await waitForAllStylesToLoad();

const documentResult = await connector.attachContentScript(() => {
const fetchCSS = () => {
let allStyles = '';
for (let i = 0; i < document.styleSheets.length; i++) {
const sheet = document.styleSheets[i];
try {
const rules = sheet.rules || sheet.cssRules;
for (let j = 0; j < rules.length; j++) {
allStyles += rules[j].cssText + '\n';
}
} catch (e) {
console.error("Can't fetch styles: ", e);
}
}
return allStyles;
};

let outerHTML = document.documentElement.outerHTML;

const stylesString = fetchCSS();
outerHTML = outerHTML.replace(/<link rel="stylesheet"[^>]+>/g, '');
outerHTML = outerHTML.replace('</head>', `<style>\n${stylesString}</style></head>`);

const scripts = [...document.scripts]
.map((script: HTMLScriptElement) => {
return script.src ? `<script src="${script.src}"></script>` : `<script>${script.innerHTML}</script>`;
})
.join('\n');

outerHTML = outerHTML.replace('</body>', `${scripts}</body>`);

return JSON.stringify({ outerHTML });
});

const result = await documentResult[0].result;
const { outerHTML } = JSON.parse(result);
return JSON.stringify(outerHTML);
};
68 changes: 42 additions & 26 deletions src/pageServices/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface ScriptMessage {
param: any;
}

/* global chrome */
///* global chrome */

/* eslint-disable */
// because we don't have proper typings for chrome object
Expand All @@ -24,7 +24,7 @@ class Connector {
onmessage: (
payload: { message: ScriptMsg; param: Record<string, never> },
sender: chrome.runtime.MessageSender,
sendResponse: (response: any) => void
sendResponse: (response: any) => void,
) => void;
onDisconnectCallback: () => void;

Expand Down Expand Up @@ -52,8 +52,15 @@ class Connector {
.then((response) => response)
.catch((error: Error) => {
if (error.message === SCRIPT_ERROR.NO_RESPONSE && isUndefined(onResponse)) return null;
if (error.message === SCRIPT_ERROR.NO_CONNECTION && action !== ScriptMsg.PingScript && action !== ScriptMsg.CheckSession && action !== ScriptMsg.KillHighlight) {
return this.onDisconnectHandler(this.port, true).then(() => this.sendMessage(action, payload, onResponse, tabId));
if (
error.message === SCRIPT_ERROR.NO_CONNECTION &&
action !== ScriptMsg.PingScript &&
action !== ScriptMsg.CheckSession &&
action !== ScriptMsg.KillHighlight
) {
return this.onDisconnectHandler(this.port, true).then(() =>
this.sendMessage(action, payload, onResponse, tabId),
);
}
return error;
})
Expand All @@ -71,8 +78,8 @@ class Connector {
callback: (
payload: ScriptMessagePayload,
sender: chrome.runtime.MessageSender,
sendResponse: (response: any) => void
) => void
sendResponse: (response: any) => void,
) => void,
) {
if (this.onmessage) chrome.runtime.onMessage.removeListener(this.onmessage);
this.onmessage = callback;
Expand All @@ -81,7 +88,7 @@ class Connector {

async onDisconnectHandler(port: any, forced?: boolean) {
if (this.port) this.port = undefined;
if (typeof this.onDisconnectCallback === "function" && !forced) this.onDisconnectCallback();
if (typeof this.onDisconnectCallback === 'function' && !forced) this.onDisconnectCallback();
return this.initScripts().then(() => this.port?.onDisconnect.addListener(this.onDisconnectHandler.bind(this)));
}

Expand All @@ -101,11 +108,11 @@ class Connector {
}
}

async attachContentScript(script: string[] | ((...args: any[]) => void), scriptName = "") {
async attachContentScript(script: string[] | ((...args: any[]) => void), scriptName = '') {
return this.scriptExists(scriptName).then((result) => {
if (result) return true;

const injection = typeof script === "function" ? { func: script } : { files: script };
const injection = typeof script === 'function' ? { func: script } : { files: script };

return chrome.scripting
.executeScript({
Expand Down Expand Up @@ -133,14 +140,14 @@ class Connector {

attachStaticScripts() {
return Promise.all([
this.attachContentScript(["contentScript.bundle.js"], "index").then(() => {
this.attachContentScript(['contentScript.bundle.js'], 'index').then(() => {
this.createPort();
chrome.storage.sync.set({ IS_DISCONNECTED: false });
sendMessage.defineTabId(this.tabId);
sendMessage.setClosedSession({ tabId: this.tabId, isClosed: false });
return "success";
return 'success';
}),
this.attachCSS("contentStyles.css"),
this.attachCSS('contentStyles.css'),
]);
}

Expand All @@ -160,41 +167,50 @@ const connector = new Connector();
export const sendMessage = {
addElement: (el: ILocator) => connector.sendMessage(ScriptMsg.AddElement, el),
assignDataLabels: (payload: PredictedEntity[]) => connector.sendMessage(ScriptMsg.AssignDataLabel, payload),
assignJdnHash: (payload: { jdnHash: string, locatorValue: string, isCSSLocator?: Boolean }) => connector.sendMessage(ScriptMsg.AssignJdnHash, payload),
assignJdnHash: (payload: { jdnHash: string; locatorValue: string; isCSSLocator?: Boolean }) =>
connector.sendMessage(ScriptMsg.AssignJdnHash, payload),
assignParents: (payload: ILocator[]) => connector.sendMessage(ScriptMsg.AssignParents, payload),
changeElementName: (el: ILocator) => connector.sendMessage(ScriptMsg.ChangeElementName, el),
changeElementType: (el: ILocator) => connector.sendMessage(ScriptMsg.ChangeElementType, el),
changeStatus: (el: ILocator) => connector.sendMessage(ScriptMsg.ChangeStatus, el),
checkSession: (payload: null, onResponse?: () => void): Promise<{ message: string; tabId: number }[]> =>
connector.sendMessageToAllTabs(ScriptMsg.CheckSession, payload, onResponse),
defineTabId: (payload: number) => connector.sendMessage(ScriptMsg.DefineTabId, payload),
evaluateXpath: (payload: { xPath: string; element_id?: ElementId, originJdnHash?: string }, onResponse?: () => void) =>
connector.sendMessage(ScriptMsg.EvaluateXpath, payload, onResponse),
evaluateStandardLocator: (payload: { selector: string; locatorType: LocatorType, element_id?: ElementId, originJdnHash?: string }, onResponse?: () => void) =>
connector.sendMessage(ScriptMsg.EvaluateStandardLocator, payload, onResponse),
getPageData: (payload?: {}, onResponse?: () => void) => connector.sendMessage(ScriptMsg.GetPageData, payload, onResponse),
generateSelectorByHash: (payload: { element_id: string, jdnHash: string }, onResponse?: () => void) =>
evaluateXpath: (
payload: { xPath: string; element_id?: ElementId; originJdnHash?: string },
onResponse?: () => void,
) => connector.sendMessage(ScriptMsg.EvaluateXpath, payload, onResponse),
evaluateStandardLocator: (
payload: { selector: string; locatorType: LocatorType; element_id?: ElementId; originJdnHash?: string },
onResponse?: () => void,
) => connector.sendMessage(ScriptMsg.EvaluateStandardLocator, payload, onResponse),
getPageData: (payload?: {}, onResponse?: () => void) =>
connector.sendMessage(ScriptMsg.GetPageData, payload, onResponse),
generateSelectorByHash: (payload: { element_id: string; jdnHash: string }, onResponse?: () => void) =>
connector.sendMessage(ScriptMsg.GenerateSelectorByHash, payload, onResponse),
generateSelectorGroupByHash: (payload: { elements: ILocator[], fireCallbackMessage?: boolean }, onResponse?: () => void) =>
connector.sendMessage(ScriptMsg.GenerateSelectorGroupByHash, payload, onResponse),
generateSelectorGroupByHash: (
payload: { elements: ILocator[]; fireCallbackMessage?: boolean },
onResponse?: () => void,
) => connector.sendMessage(ScriptMsg.GenerateSelectorGroupByHash, payload, onResponse),
findBySelectors: (payload: SelectorsMap) => connector.sendMessage(ScriptMsg.FindBySelectors, payload),
setClosedSession: (payload: { tabId: number; isClosed: boolean }) =>
connector.sendMessage(ScriptMsg.SetClosedSession, payload),
setHighlight: (payload: { elements?: ILocator[]; filter?: ClassFilterValue; isAlreadyGenerated?: boolean }) =>
connector.sendMessage(ScriptMsg.SetHighlight, payload),
killHighlight: (payload?: {}, onResponse?: () => void) => connector.sendMessage(ScriptMsg.KillHighlight, null, onResponse),
generateAttributes: (payload: { elements: PredictedEntity[], generateCss: boolean }, onResponse?: () => void) =>
killHighlight: (payload?: {}, onResponse?: () => void) =>
connector.sendMessage(ScriptMsg.KillHighlight, null, onResponse),
generateAttributes: (payload: { elements: PredictedEntity[]; generateCss: boolean }, onResponse?: () => void) =>
connector.sendMessage(ScriptMsg.GenerateAttributes, payload, onResponse),
getElementXpath: (payload: string, onResponse?: () => void) =>
connector.sendMessage(ScriptMsg.GetElementXpath, payload, onResponse),
pingScript: (payload: { scriptName: string }, onResponse?: () => void) =>
connector.sendMessage(ScriptMsg.PingScript, payload, onResponse),
removeElement: (payload: ILocator) => connector.sendMessage(ScriptMsg.RemoveElement, payload),
setActive: (payload: ILocator | ILocator[]) => connector.sendMessage(ScriptMsg.SetActive, payload),
toggle: (payload: { element: ILocator; skipScroll?: boolean }) => connector.sendMessage(ScriptMsg.HighlightToggled, payload),
toggle: (payload: { element: ILocator; skipScroll?: boolean }) =>
connector.sendMessage(ScriptMsg.HighlightToggled, payload),
toggleDeleted: (el: ILocator) => connector.sendMessage(ScriptMsg.ToggleDeleted, el),
toggleFilter: (payload: ClassFilterValue) =>
connector.sendMessage(ScriptMsg.ToggleFilter, payload),
toggleFilter: (payload: ClassFilterValue) => connector.sendMessage(ScriptMsg.ToggleFilter, payload),
toggleActiveGroup: (payload: ILocator[]) => connector.sendMessage(ScriptMsg.ToggleActiveGroup, payload),
unsetActive: (payload: ILocator | ILocator[]) => connector.sendMessage(ScriptMsg.UnsetActive, payload),
};
Expand Down
4 changes: 2 additions & 2 deletions src/pageServices/pageDataHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import connector, { sendMessage } from './connector';
import { request } from '../services/backend';
import { createOverlay } from './contentScripts/createOverlay';
import { getFullDocument } from '../common/utils/getFullDocument';
import { getFullDocumentWithStyles } from '../common/utils/getFullDocumentWithStyles';
// /* global chrome */

let overlayID;
Expand Down Expand Up @@ -36,7 +36,7 @@ And then tha data is sent to endpoint, according to selected library.
Function returns predicted elements. */
export const predictElements = (endpoint) => {
let pageData;
return Promise.all([sendMessage.getPageData(), getFullDocument()])
return Promise.all([sendMessage.getPageData(), getFullDocumentWithStyles()])
.then(([pageDataResult, documentResult]) => {
pageData = pageDataResult[0];
return sendToModel({ elements: pageData, document: documentResult }, endpoint);
Expand Down
Loading
Loading