diff --git a/__snapshots__/LighthouseStringifyExtension.test.ts.js b/__snapshots__/LighthouseStringifyExtension.test.ts.js index 721a813d..889acb46 100644 --- a/__snapshots__/LighthouseStringifyExtension.test.ts.js +++ b/__snapshots__/LighthouseStringifyExtension.test.ts.js @@ -28,8 +28,8 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later await lhFlow.startTimespan(); { const targetPage = page; + await scrollIntoViewIfNeeded([["#button"]], targetPage, timeout); const element = await waitForSelectors([["#button"]], targetPage, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); await element.click({ offset: { x: 61, @@ -74,8 +74,8 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later await lhFlow.startTimespan(); { const targetPage = page; + await scrollIntoViewIfNeeded([["#button"]], targetPage, timeout); const element = await waitForSelectors([["#button"]], targetPage, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); await element.click({ offset: { x: 61, @@ -133,8 +133,8 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later const targetPage = page; const promises = []; promises.push(targetPage.waitForNavigation()); + await scrollIntoViewIfNeeded([["#link"]], targetPage, timeout); const element = await waitForSelectors([["#link"]], targetPage, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); await element.click({ offset: { x: 61, diff --git a/__snapshots__/PuppeteerStringifyExtension.test.ts.js b/__snapshots__/PuppeteerStringifyExtension.test.ts.js index f04b5403..31768cb1 100644 --- a/__snapshots__/PuppeteerStringifyExtension.test.ts.js +++ b/__snapshots__/PuppeteerStringifyExtension.test.ts.js @@ -3,8 +3,8 @@ exports[ ] = ` { const targetPage = page; + await scrollIntoViewIfNeeded(["aria/Test"], targetPage, timeout); const element = await waitForSelectors(["aria/Test"], targetPage, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); await element.click({ offset: { x: 1, @@ -22,8 +22,8 @@ exports[ const targetPage = page; const promises = []; promises.push(targetPage.waitForNavigation()); + await scrollIntoViewIfNeeded(["aria/Test"], targetPage, timeout); const element = await waitForSelectors(["aria/Test"], targetPage, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); await element.click({ offset: { x: 1, @@ -40,8 +40,8 @@ exports[ ] = ` { const targetPage = page; + await scrollIntoViewIfNeeded([["aria/Test","aria/Test2"]], targetPage, timeout); const element = await waitForSelectors([["aria/Test","aria/Test2"]], targetPage, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); await element.click({ offset: { x: 1, @@ -57,8 +57,8 @@ exports[ ] = ` { const targetPage = page; + await scrollIntoViewIfNeeded(["aria/Test"], targetPage, timeout); const element = await waitForSelectors(["aria/Test"], targetPage, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); const type = await element.evaluate(el => el.type); if (["select-one"].includes(type)) { await element.select("Hello World"); @@ -81,8 +81,8 @@ exports[ ] = ` { const targetPage = page; + await scrollIntoViewIfNeeded(["aria/Test"], targetPage, timeout); const element = await waitForSelectors(["aria/Test"], targetPage, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); const type = await element.evaluate(el => el.type); if (["select-one"].includes(type)) { await element.select("#333333"); diff --git a/__snapshots__/lighthouse-e2e.test.ts.js b/__snapshots__/lighthouse-e2e.test.ts.js index 417787a2..2e1b6588 100644 --- a/__snapshots__/lighthouse-e2e.test.ts.js +++ b/__snapshots__/lighthouse-e2e.test.ts.js @@ -27,8 +27,8 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later await lhFlow.startTimespan(); { const targetPage = page; + await scrollIntoViewIfNeeded(["#test"], targetPage, timeout); const element = await waitForSelectors(["#test"], targetPage, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); await element.click({ button: 'left', offset: { @@ -39,8 +39,8 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later } { const targetPage = page; + await scrollIntoViewIfNeeded(["#test"], targetPage, timeout); const element = await waitForSelectors(["#test"], targetPage, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); await element.click({ button: 'middle', offset: { @@ -55,8 +55,8 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later const targetPage = page; const promises = []; promises.push(targetPage.waitForNavigation()); + await scrollIntoViewIfNeeded(["a[href=\\"main2.html\\"]"], targetPage, timeout); const element = await waitForSelectors(["a[href=\\"main2.html\\"]"], targetPage, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); await element.click({ offset: { x: 1, @@ -69,8 +69,8 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later await lhFlow.startTimespan(); { const targetPage = page; + await scrollIntoViewIfNeeded(["#test"], targetPage, timeout); const element = await waitForSelectors(["#test"], targetPage, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); await element.click({ button: 'left', offset: { @@ -81,8 +81,8 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later } { const targetPage = page; + await scrollIntoViewIfNeeded(["#test"], targetPage, timeout); const element = await waitForSelectors(["#test"], targetPage, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); await element.click({ button: 'left', offset: { @@ -108,7 +108,13 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later throw new Error('Could not find element for selectors: ' + JSON.stringify(selectors)); } - async function scrollIntoViewIfNeeded(element, timeout) { + async function scrollIntoViewIfNeeded(selectors, frame, timeout) { + const element = await waitForSelectors(selectors, frame, { visible: false, timeout }); + if (!element) { + throw new Error( + 'The element could not be found.' + ); + } await waitForConnected(element, timeout); const isInViewport = await element.isIntersectingViewport({threshold: 0}); if (isInViewport) { diff --git a/__snapshots__/stringify.test.ts.js b/__snapshots__/stringify.test.ts.js index 7944464b..7bc48b0f 100644 --- a/__snapshots__/stringify.test.ts.js +++ b/__snapshots__/stringify.test.ts.js @@ -25,7 +25,13 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later throw new Error('Could not find element for selectors: ' + JSON.stringify(selectors)); } - async function scrollIntoViewIfNeeded(element, timeout) { + async function scrollIntoViewIfNeeded(selectors, frame, timeout) { + const element = await waitForSelectors(selectors, frame, { visible: false, timeout }); + if (!element) { + throw new Error( + 'The element could not be found.' + ); + } await waitForConnected(element, timeout); const isInViewport = await element.isIntersectingViewport({threshold: 0}); if (isInViewport) { @@ -198,7 +204,13 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later throw new Error('Could not find element for selectors: ' + JSON.stringify(selectors)); } - async function scrollIntoViewIfNeeded(element, timeout) { + async function scrollIntoViewIfNeeded(selectors, frame, timeout) { + const element = await waitForSelectors(selectors, frame, { visible: false, timeout }); + if (!element) { + throw new Error( + 'The element could not be found.' + ); + } await waitForConnected(element, timeout); const isInViewport = await element.isIntersectingViewport({threshold: 0}); if (isInViewport) { @@ -352,8 +364,8 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later const target = await browser.waitForTarget(t => t.url() === "https://localhost/test", { timeout }); const targetPage = await target.page(); targetPage.setDefaultTimeout(timeout); + await scrollIntoViewIfNeeded(["aria/Test"], targetPage, timeout); const element = await waitForSelectors(["aria/Test"], targetPage, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); await element.click({ offset: { x: 1, @@ -375,7 +387,13 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later throw new Error('Could not find element for selectors: ' + JSON.stringify(selectors)); } - async function scrollIntoViewIfNeeded(element, timeout) { + async function scrollIntoViewIfNeeded(selectors, frame, timeout) { + const element = await waitForSelectors(selectors, frame, { visible: false, timeout }); + if (!element) { + throw new Error( + 'The element could not be found.' + ); + } await waitForConnected(element, timeout); const isInViewport = await element.isIntersectingViewport({threshold: 0}); if (isInViewport) { @@ -528,8 +546,8 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later const target = await browser.waitForTarget(t => t.url() === "https://localhost/test", { timeout }); const targetPage = await target.page(); targetPage.setDefaultTimeout(timeout); + await scrollIntoViewIfNeeded(["aria/Test"], targetPage, timeout); const element = await waitForSelectors(["aria/Test"], targetPage, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); await element.click({ offset: { x: 1, @@ -551,7 +569,13 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later throw new Error('Could not find element for selectors: ' + JSON.stringify(selectors)); } - async function scrollIntoViewIfNeeded(element, timeout) { + async function scrollIntoViewIfNeeded(selectors, frame, timeout) { + const element = await waitForSelectors(selectors, frame, { visible: false, timeout }); + if (!element) { + throw new Error( + 'The element could not be found.' + ); + } await waitForConnected(element, timeout); const isInViewport = await element.isIntersectingViewport({threshold: 0}); if (isInViewport) { @@ -706,8 +730,8 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later let frame = targetPage.mainFrame(); frame = frame.childFrames()[1]; frame = frame.childFrames()[1]; + await scrollIntoViewIfNeeded(["aria/Test"], frame, timeout); const element = await waitForSelectors(["aria/Test"], frame, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); await element.click({ offset: { x: 1, @@ -729,7 +753,13 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later throw new Error('Could not find element for selectors: ' + JSON.stringify(selectors)); } - async function scrollIntoViewIfNeeded(element, timeout) { + async function scrollIntoViewIfNeeded(selectors, frame, timeout) { + const element = await waitForSelectors(selectors, frame, { visible: false, timeout }); + if (!element) { + throw new Error( + 'The element could not be found.' + ); + } await waitForConnected(element, timeout); const isInViewport = await element.isIntersectingViewport({threshold: 0}); if (isInViewport) { @@ -895,7 +925,13 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later throw new Error('Could not find element for selectors: ' + JSON.stringify(selectors)); } - async function scrollIntoViewIfNeeded(element, timeout) { + async function scrollIntoViewIfNeeded(selectors, frame, timeout) { + const element = await waitForSelectors(selectors, frame, { visible: false, timeout }); + if (!element) { + throw new Error( + 'The element could not be found.' + ); + } await waitForConnected(element, timeout); const isInViewport = await element.isIntersectingViewport({threshold: 0}); if (isInViewport) { @@ -1061,7 +1097,13 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later throw new Error('Could not find element for selectors: ' + JSON.stringify(selectors)); } - async function scrollIntoViewIfNeeded(element, timeout) { + async function scrollIntoViewIfNeeded(selectors, frame, timeout) { + const element = await waitForSelectors(selectors, frame, { visible: false, timeout }); + if (!element) { + throw new Error( + 'The element could not be found.' + ); + } await waitForConnected(element, timeout); const isInViewport = await element.isIntersectingViewport({threshold: 0}); if (isInViewport) { @@ -1211,8 +1253,8 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later { const targetPage = page; + await scrollIntoViewIfNeeded(["body > div:nth-child(1)"], targetPage, timeout); const element = await waitForSelectors(["body > div:nth-child(1)"], targetPage, { timeout, visible: true }); - await scrollIntoViewIfNeeded(element, timeout); await element.evaluate((el, x, y) => { el.scrollTop = y; el.scrollLeft = x; }, 0, 40); } { @@ -1233,7 +1275,13 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later throw new Error('Could not find element for selectors: ' + JSON.stringify(selectors)); } - async function scrollIntoViewIfNeeded(element, timeout) { + async function scrollIntoViewIfNeeded(selectors, frame, timeout) { + const element = await waitForSelectors(selectors, frame, { visible: false, timeout }); + if (!element) { + throw new Error( + 'The element could not be found.' + ); + } await waitForConnected(element, timeout); const isInViewport = await element.isIntersectingViewport({threshold: 0}); if (isInViewport) { @@ -1401,7 +1449,13 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later throw new Error('Could not find element for selectors: ' + JSON.stringify(selectors)); } - async function scrollIntoViewIfNeeded(element, timeout) { + async function scrollIntoViewIfNeeded(selectors, frame, timeout) { + const element = await waitForSelectors(selectors, frame, { visible: false, timeout }); + if (!element) { + throw new Error( + 'The element could not be found.' + ); + } await waitForConnected(element, timeout); const isInViewport = await element.isIntersectingViewport({threshold: 0}); if (isInViewport) { @@ -1569,7 +1623,13 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later throw new Error('Could not find element for selectors: ' + JSON.stringify(selectors)); } - async function scrollIntoViewIfNeeded(element, timeout) { + async function scrollIntoViewIfNeeded(selectors, frame, timeout) { + const element = await waitForSelectors(selectors, frame, { visible: false, timeout }); + if (!element) { + throw new Error( + 'The element could not be found.' + ); + } await waitForConnected(element, timeout); const isInViewport = await element.isIntersectingViewport({threshold: 0}); if (isInViewport) { diff --git a/src/PuppeteerRunnerExtension.ts b/src/PuppeteerRunnerExtension.ts index 3efff5e1..88094058 100644 --- a/src/PuppeteerRunnerExtension.ts +++ b/src/PuppeteerRunnerExtension.ts @@ -111,6 +111,7 @@ export class PuppeteerRunnerExtension extends RunnerExtension { switch (step.type) { case 'doubleClick': { + await scrollIntoViewIfNeeded(step.selectors, localFrame, timeout); const element = await waitForSelectors(step.selectors, localFrame, { timeout, visible: waitForVisible, @@ -118,7 +119,6 @@ export class PuppeteerRunnerExtension extends RunnerExtension { if (!element) { throw new Error('Could not find element: ' + step.selectors[0]); } - await scrollIntoViewIfNeeded(element, timeout); startWaitingForEvents(); await element.click({ clickCount: 2, @@ -133,6 +133,7 @@ export class PuppeteerRunnerExtension extends RunnerExtension { break; case 'click': { + await scrollIntoViewIfNeeded(step.selectors, localFrame, timeout); const element = await waitForSelectors(step.selectors, localFrame, { timeout, visible: waitForVisible, @@ -140,7 +141,6 @@ export class PuppeteerRunnerExtension extends RunnerExtension { if (!element) { throw new Error('Could not find element: ' + step.selectors[0]); } - await scrollIntoViewIfNeeded(element, timeout); startWaitingForEvents(); await element.click({ delay: step.duration, @@ -155,6 +155,7 @@ export class PuppeteerRunnerExtension extends RunnerExtension { break; case 'hover': { + await scrollIntoViewIfNeeded(step.selectors, localFrame, timeout); const element = await waitForSelectors(step.selectors, localFrame, { timeout, visible: waitForVisible, @@ -162,7 +163,6 @@ export class PuppeteerRunnerExtension extends RunnerExtension { if (!element) { throw new Error('Could not find element: ' + step.selectors[0]); } - await scrollIntoViewIfNeeded(element, timeout); startWaitingForEvents(); await element.hover(); await element.dispose(); @@ -198,11 +198,14 @@ export class PuppeteerRunnerExtension extends RunnerExtension { break; case 'change': { + await scrollIntoViewIfNeeded(step.selectors, localFrame, timeout); const element = (await waitForSelectors(step.selectors, localFrame, { timeout, visible: waitForVisible, })) as ElementHandle; - await scrollIntoViewIfNeeded(element, timeout); + if (!element) { + throw new Error('Could not find element: ' + step.selectors[0]); + } const inputType = await element.evaluate( /* c8 ignore start */ (el) => el.type @@ -228,11 +231,11 @@ export class PuppeteerRunnerExtension extends RunnerExtension { } case 'scroll': { if ('selectors' in step) { + await scrollIntoViewIfNeeded(step.selectors, localFrame, timeout); const element = await waitForSelectors(step.selectors, localFrame, { timeout, visible: waitForVisible, }); - await scrollIntoViewIfNeeded(element, timeout); startWaitingForEvents(); await element.evaluate( (e, x, y) => { @@ -461,9 +464,17 @@ const asSVGElementHandle = async ( }; async function scrollIntoViewIfNeeded( - element: ElementHandle, + selectors: Selector[], + frame: Frame, timeout: number ): Promise { + const element = await waitForSelectors(selectors, frame, { + visible: false, + timeout, + }); + if (!element) { + throw new Error('The element could not be found.'); + } await waitForConnected(element, timeout); const svgHandle = await asSVGElementHandle(element); const intersectionTarget = svgHandle diff --git a/src/PuppeteerStringifyExtension.ts b/src/PuppeteerStringifyExtension.ts index 1aa37935..f9be1146 100644 --- a/src/PuppeteerStringifyExtension.ts +++ b/src/PuppeteerStringifyExtension.ts @@ -133,12 +133,16 @@ export class PuppeteerStringifyExtension extends StringifyExtension { } #appendWaitForSelector(out: LineWriter, step: StepWithSelectors): void { + out.appendLine( + `await scrollIntoViewIfNeeded(${JSON.stringify(step.selectors)}, ${ + step.frame ? 'frame' : 'targetPage' + }, timeout);` + ); out.appendLine( `const element = await waitForSelectors(${JSON.stringify( step.selectors )}, ${step.frame ? 'frame' : 'targetPage'}, { timeout, visible: true });` ); - out.appendLine('await scrollIntoViewIfNeeded(element, timeout);'); } #appendClickStep(out: LineWriter, step: ClickStep): void { @@ -329,7 +333,13 @@ const helpers = `async function waitForSelectors(selectors, frame, options) { throw new Error('Could not find element for selectors: ' + JSON.stringify(selectors)); } -async function scrollIntoViewIfNeeded(element, timeout) { +async function scrollIntoViewIfNeeded(selectors, frame, timeout) { + const element = await waitForSelectors(selectors, frame, { visible: false, timeout }); + if (!element) { + throw new Error( + 'The element could not be found.' + ); + } await waitForConnected(element, timeout); const isInViewport = await element.isIntersectingViewport({threshold: 0}); if (isInViewport) { diff --git a/test/runner.test.ts b/test/runner.test.ts index a114c005..a668dff3 100644 --- a/test/runner.test.ts +++ b/test/runner.test.ts @@ -609,7 +609,7 @@ describe('Runner', () => { ); }); - it.only('should be able to replay text selectors', async () => { + it('should be able to replay text selectors', async () => { const runner = await createRunner( { title: 'test',