fix(trace): allow typing in selector w/ frames (#26919)

This commit is contained in:
Pavel Feldman 2023-09-06 16:14:40 -07:00 committed by GitHub
parent ea4974ce36
commit d65da74b8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 16 deletions

View File

@ -53,7 +53,7 @@ export class Recorder {
console.error('Recorder script ready for test'); // eslint-disable-line no-console
}
refreshListenersIfNeeded() {
installListeners() {
// Ensure we are attached to the current document, and we are on top (last element);
if (this._highlight.isInstalled())
return;
@ -74,19 +74,24 @@ export class Recorder {
return;
this._hoveredModel = null;
this._highlight.hideActionPoint();
this._updateHighlight();
this._updateHighlight(false);
}, true),
];
this._highlight.install();
}
uninstallListeners() {
removeEventListeners(this._listeners);
this._highlight.uninstall();
}
setUIState(state: UIState, delegate: RecorderDelegate) {
this._delegate = delegate;
if (state.mode !== 'none' || state.actionSelector)
this.refreshListenersIfNeeded();
this.installListeners();
else
removeEventListeners(this._listeners);
this.uninstallListeners();
const { mode, actionPoint, actionSelector, language, testIdAttributeName } = state;
this._testIdAttributeName = testIdAttributeName;
@ -113,7 +118,7 @@ export class Recorder {
if (actionSelector !== this._actionSelector) {
this._hoveredModel = actionSelector ? querySelector(this._injectedScript, actionSelector, this.document) : null;
this._updateHighlight();
this._updateHighlight(false);
this._actionSelector = actionSelector;
}
}
@ -121,7 +126,7 @@ export class Recorder {
clearHighlight() {
this._hoveredModel = null;
this._activeModel = null;
this._updateHighlight();
this._updateHighlight(false);
}
private _actionInProgress(event: Event): boolean {
@ -257,7 +262,7 @@ export class Recorder {
if (!this._hoveredElement || !this._hoveredElement.isConnected) {
this._hoveredModel = null;
this._hoveredElement = null;
this._updateHighlight();
this._updateHighlight(true);
return;
}
const hoveredElement = this._hoveredElement;
@ -265,14 +270,14 @@ export class Recorder {
if ((this._hoveredModel && this._hoveredModel.selector === selector))
return;
this._hoveredModel = selector ? { selector, elements } : null;
this._updateHighlight();
this._updateHighlight(true);
}
private _updateHighlight() {
private _updateHighlight(userGesture: boolean) {
const elements = this._hoveredModel ? this._hoveredModel.elements : [];
const selector = this._hoveredModel ? this._hoveredModel.selector : '';
this._highlight.updateHighlight(elements, selector, this._mode === 'recording');
if (this._hoveredModel)
if (userGesture)
this._delegate.highlightUpdated?.();
}
@ -522,7 +527,7 @@ export class PollingRecorder implements RecorderDelegate {
this._recorder = new Recorder(injectedScript);
this._embedder = injectedScript.window as any;
injectedScript.onGlobalListenersRemoved.add(() => this._recorder.refreshListenersIfNeeded());
injectedScript.onGlobalListenersRemoved.add(() => this._recorder.installListeners());
const refreshOverlay = () => {
this._pollRecorderMode().catch(e => console.log(e)); // eslint-disable-line no-console

View File

@ -228,8 +228,9 @@ export const InspectModeController: React.FunctionComponent<{
}> = ({ iframe, isInspecting, sdkLanguage, testIdAttributeName, highlightedLocator, setHighlightedLocator, iteration }) => {
React.useEffect(() => {
const recorders: { recorder: Recorder, frameSelector: string }[] = [];
const isUnderTest = new URLSearchParams(window.location.search).get('isUnderTest') === 'true';
try {
createRecorders(recorders, sdkLanguage, testIdAttributeName, '', iframe?.contentWindow);
createRecorders(recorders, sdkLanguage, testIdAttributeName, isUnderTest, '', iframe?.contentWindow);
} catch {
// Potential cross-origin exceptions.
}
@ -238,7 +239,7 @@ export const InspectModeController: React.FunctionComponent<{
const actionSelector = locatorOrSelectorAsSelector(sdkLanguage, highlightedLocator, testIdAttributeName);
recorder.setUIState({
mode: isInspecting ? 'inspecting' : 'none',
actionSelector,
actionSelector: actionSelector.startsWith(frameSelector) ? actionSelector.substring(frameSelector.length).trim() : undefined,
language: sdkLanguage,
testIdAttributeName,
}, {
@ -257,12 +258,12 @@ export const InspectModeController: React.FunctionComponent<{
return <></>;
};
function createRecorders(recorders: { recorder: Recorder, frameSelector: string }[], sdkLanguage: Language, testIdAttributeName: string, parentFrameSelector: string, frameWindow: Window | null | undefined) {
function createRecorders(recorders: { recorder: Recorder, frameSelector: string }[], sdkLanguage: Language, testIdAttributeName: string, isUnderTest: boolean, parentFrameSelector: string, frameWindow: Window | null | undefined) {
if (!frameWindow)
return;
const win = frameWindow as any;
if (!win._recorder) {
const injectedScript = new InjectedScript(frameWindow as any, false, sdkLanguage, testIdAttributeName, 1, 'chromium', []);
const injectedScript = new InjectedScript(frameWindow as any, isUnderTest, sdkLanguage, testIdAttributeName, 1, 'chromium', []);
const recorder = new Recorder(injectedScript);
win._injectedScript = injectedScript;
win._recorder = { recorder, frameSelector: parentFrameSelector };
@ -272,7 +273,7 @@ function createRecorders(recorders: { recorder: Recorder, frameSelector: string
for (let i = 0; i < frameWindow.frames.length; ++i) {
const childFrame = frameWindow.frames[i];
const frameSelector = childFrame.frameElement ? win._injectedScript.generateSelector(childFrame.frameElement, { omitInternalEngines: true, testIdAttributeName }) + ' >> internal:control=enter-frame >> ' : '';
createRecorders(recorders, sdkLanguage, testIdAttributeName, parentFrameSelector + frameSelector, childFrame);
createRecorders(recorders, sdkLanguage, testIdAttributeName, isUnderTest, parentFrameSelector + frameSelector, childFrame);
}
}

View File

@ -1034,3 +1034,58 @@ test('should pick locator in iframe', async ({ page, runAndTrace, server }) => {
await snapshot.frameLocator('#frame1').frameLocator('iframe').frameLocator('[name=two]').getByText('HelloNameTwo').click();
await expect.soft(cmWrapper).toContainText(`frameLocator('#frame1').frameLocator('iframe').frameLocator('iframe[name="two"]').getByText('HelloNameTwo')`, { timeout: 0 });
});
test('should highlight locator in iframe while typing', async ({ page, runAndTrace, server, platform }) => {
/*
iframe[id=frame1]
div Hello1
iframe
div Hello2
iframe[name=one]
div HelloNameOne
iframe[name=two]
dev HelloNameTwo
*/
const traceViewer = await runAndTrace(async () => {
await page.goto(server.EMPTY_PAGE);
await page.setContent(`<iframe id=frame1 srcdoc="<div>Hello1</div><iframe srcdoc='<div>Hello2</div><iframe name=one></iframe><iframe name=two></iframe><iframe></iframe>'>">`);
const frameOne = page.frame({ name: 'one' });
await frameOne.setContent(`<div>HelloNameOne</div>`);
const frameTwo = page.frame({ name: 'two' });
await frameTwo.setContent(`<div>HelloNameTwo</div>`);
await page.evaluate('2+2');
});
const snapshot = await traceViewer.snapshotFrame('page.evaluate');
await traceViewer.page.getByText('Locator').click();
await traceViewer.page.locator('.CodeMirror').click();
const locators = [{
text: `frameLocator('#frame1').getByText('Hello1')`,
element: snapshot.frameLocator('#frame1').locator('div', { hasText: 'Hello1' }),
highlight: snapshot.frameLocator('#frame1').locator('x-pw-highlight'),
}, {
text: `frameLocator('#frame1').frameLocator('iframe').getByText('Hello2')`,
element: snapshot.frameLocator('#frame1').frameLocator('iframe').locator('div', { hasText: 'Hello2' }),
highlight: snapshot.frameLocator('#frame1').frameLocator('iframe').locator('x-pw-highlight'),
}, {
text: `frameLocator('#frame1').frameLocator('iframe').frameLocator('iframe[name="one"]').getByText('HelloNameOne')`,
element: snapshot.frameLocator('#frame1').frameLocator('iframe').frameLocator('iframe[name="one"]').locator('div', { hasText: 'HelloNameOne' }),
highlight: snapshot.frameLocator('#frame1').frameLocator('iframe').frameLocator('iframe[name="one"]').locator('x-pw-highlight'),
}];
for (const locator of locators) {
if (platform === 'darwin')
await traceViewer.page.keyboard.press('Meta+a');
else
await traceViewer.page.keyboard.press('Control+a');
await traceViewer.page.keyboard.press('Backspace');
await traceViewer.page.keyboard.type(locator.text);
const elementBox = await locator.element.boundingBox();
const highlightBox = await locator.highlight.boundingBox();
expect(Math.abs(elementBox.width - highlightBox.width)).toBeLessThan(5);
expect(Math.abs(elementBox.height - highlightBox.height)).toBeLessThan(5);
expect(Math.abs(elementBox.x - highlightBox.x)).toBeLessThan(5);
expect(Math.abs(elementBox.y - highlightBox.y)).toBeLessThan(5);
}
});