fix(debug controller): highlight selectors in iframe

This commit is contained in:
Simon Knott 2024-10-24 16:57:16 +02:00
parent 67471cb3c5
commit 95a7a757f0
4 changed files with 59 additions and 15 deletions

View file

@ -19,7 +19,7 @@ import { XPathEngine } from './xpathSelectorEngine';
import { ReactEngine } from './reactSelectorEngine';
import { VueEngine } from './vueSelectorEngine';
import { createRoleEngine } from './roleSelectorEngine';
import { parseAttributeSelector } from '../../utils/isomorphic/selectorParser';
import { parseAttributeSelector, splitSelectorByFrame } from '../../utils/isomorphic/selectorParser';
import type { NestedSelectorBody, ParsedSelector, ParsedSelectorPart } from '../../utils/isomorphic/selectorParser';
import { visitAllSelectorParts, parseSelector, stringifySelector } from '../../utils/isomorphic/selectorParser';
import { type TextMatcher, elementMatchesText, elementText, type ElementText, getElementLabels } from './selectorUtils';
@ -166,12 +166,23 @@ export class InjectedScript {
return this._testIdAttributeNameForStrictErrorAndConsoleCodegen;
}
parseSelector(selector: string): ParsedSelector {
const result = parseSelector(selector);
visitAllSelectorParts(result, part => {
private _checkForUnknownEngine(parsed: ParsedSelector, selector: string) {
visitAllSelectorParts(parsed, part => {
if (!this._engines.has(part.name))
throw this.createStacklessError(`Unknown engine "${part.name}" while parsing selector ${selector}`);
});
}
parseSelector(selector: string): ParsedSelector {
const result = parseSelector(selector);
this._checkForUnknownEngine(result, selector);
return result;
}
splitSelectorByFrame(selector: string): ParsedSelector[] {
const result = splitSelectorByFrame(selector);
for (const part of result)
this._checkForUnknownEngine(part, selector);
return result;
}

View file

@ -21,6 +21,7 @@ import type { ElementInfo, Mode, OverlayState, UIState } from '@recorder/recorde
import type { ElementText } from '../selectorUtils';
import type { Highlight, HighlightOptions } from '../highlight';
import clipPaths from './clipPaths';
import { parseSelector, splitSelectorByFrame } from '@isomorphic/selectorParser';
export interface RecorderDelegate {
performAction?(action: actions.PerformOnRecordAction): Promise<void>;
@ -1471,18 +1472,29 @@ function removeEventListeners(listeners: (() => void)[]) {
listeners.splice(0, listeners.length);
}
function frameDepth() {
let depth = 0;
// eslint-disable-next-line no-restricted-globals
let w: Window = window;
while (w.parent !== w) {
w = w.parent;
depth++;
}
return depth;
}
function querySelector(injectedScript: InjectedScript, selector: string, ownerDocument: Document): { selector: string, elements: Element[] } {
const empty = { selector, elements: [] };
try {
const parsedSelector = injectedScript.parseSelector(selector);
return {
selector,
elements: injectedScript.querySelectorAll(parsedSelector, ownerDocument)
};
// selector starts from the root frame, we need to figure out if this frame is the right one
// as a cheap heuristic, we check if it targets the right depth
const parts = injectedScript.splitSelectorByFrame(selector);
const selectorTargetsFrameDepth = parts.length - 1 === frameDepth();
if (selectorTargetsFrameDepth)
return { selector, elements: injectedScript.querySelectorAll(parts[parts.length - 1], ownerDocument) };
return empty;
} catch (e) {
return {
selector,
elements: [],
};
return empty;
}
}

View file

@ -246,8 +246,10 @@ export class Recorder implements InstrumentationListener, IRecorder {
}
private _refreshOverlay() {
for (const page of this._context.pages())
page.mainFrame().evaluateExpression('window.__pw_refreshOverlay()').catch(() => {});
for (const page of this._context.pages()) {
for (const frame of page.frames())
frame.evaluateExpression('window.__pw_refreshOverlay()').catch(() => {});
}
}
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) {

View file

@ -253,3 +253,22 @@ test('should reset routes before reuse', async ({ server, connectedBrowserFactor
await expect(page2).toHaveTitle('console.log test');
await browser2.close();
});
test('should highlight inside iframe', async ({ backend, connectedBrowser }, testInfo) => {
testInfo.annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/33146' });
const context = await connectedBrowser._newContextForReuse();
const page = await context.newPage();
await backend.navigate({ url: `data:text/html,<iframe srcdoc="<div>bar</div>"/>` });
await page.frameLocator('iframe').getByText('bar').highlight();
const highlight = page.frameLocator('iframe').locator('x-pw-highlight');
await expect(highlight).not.toHaveCount(0);
await backend.hideHighlight();
await expect(highlight).toHaveCount(0);
await backend.highlight({ selector: `frameLocator('iframe').getByText('bar')` });
await expect(highlight).not.toHaveCount(0);
});