diff --git a/.mocharc.cjs b/.mocharc.cjs index d6e9ccc9..540a8b35 100644 --- a/.mocharc.cjs +++ b/.mocharc.cjs @@ -1,11 +1,10 @@ module.exports = { - reporter: 'dot', + reporter: 'spec', // Allow `console.log`s to show up during test execution logLevel: 'debug', exit: !!process.env.CI, spec: 'test/**/*.test.ts', extension: ['ts'], timeout: 25 * 1000, - reporter: process.env.CI ? 'spec' : 'dot', loader: 'ts-node/esm', }; diff --git a/__snapshots__/PuppeteerStringifyExtension.test.ts.js b/__snapshots__/PuppeteerStringifyExtension.test.ts.js index 31768cb1..1543aedb 100644 --- a/__snapshots__/PuppeteerStringifyExtension.test.ts.js +++ b/__snapshots__/PuppeteerStringifyExtension.test.ts.js @@ -59,18 +59,13 @@ exports[ const targetPage = page; await scrollIntoViewIfNeeded(["aria/Test"], targetPage, timeout); const element = await waitForSelectors(["aria/Test"], targetPage, { timeout, visible: true }); - const type = await element.evaluate(el => el.type); - if (["select-one"].includes(type)) { - await element.select("Hello World"); - } else if (["textarea","text","url","tel","search","password","number","email"].includes(type)) { - await element.type("Hello World"); + const inputType = await element.evaluate(el => el.type); + if (inputType === 'select-one') { + await changeSelectElement(element, "Hello World") + } else if (["textarea","text","url","tel","search","password","number","email"].includes(inputType)) { + await typeIntoElement(element, "Hello World"); } else { - await element.focus(); - await element.evaluate((el, value) => { - el.value = value; - el.dispatchEvent(new Event('input', { bubbles: true })); - el.dispatchEvent(new Event('change', { bubbles: true })); - }, "Hello World"); + await changeElementValue(element, "Hello World"); } } @@ -83,18 +78,13 @@ exports[ const targetPage = page; await scrollIntoViewIfNeeded(["aria/Test"], targetPage, timeout); const element = await waitForSelectors(["aria/Test"], targetPage, { timeout, visible: true }); - const type = await element.evaluate(el => el.type); - if (["select-one"].includes(type)) { - await element.select("#333333"); - } else if (["textarea","text","url","tel","search","password","number","email"].includes(type)) { - await element.type("#333333"); + const inputType = await element.evaluate(el => el.type); + if (inputType === 'select-one') { + await changeSelectElement(element, "#333333") + } else if (["textarea","text","url","tel","search","password","number","email"].includes(inputType)) { + await typeIntoElement(element, "#333333"); } else { - await element.focus(); - await element.evaluate((el, value) => { - el.value = value; - el.dispatchEvent(new Event('input', { bubbles: true })); - el.dispatchEvent(new Event('change', { bubbles: true })); - }, "#333333"); + await changeElementValue(element, "#333333"); } } diff --git a/__snapshots__/lighthouse-e2e.test.ts.js b/__snapshots__/lighthouse-e2e.test.ts.js index 2e1b6588..9d68f4b4 100644 --- a/__snapshots__/lighthouse-e2e.test.ts.js +++ b/__snapshots__/lighthouse-e2e.test.ts.js @@ -246,6 +246,40 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later } throw new Error('Timed out'); } + + async function changeSelectElement(element, value) { + await element.select(value); + await element.evaluateHandle((e) => { + e.blur(); + e.focus(); + }); + } + + async function changeElementValue(element, value) { + await element.focus(); + await element.evaluate((input, value) => { + input.value = value; + input.dispatchEvent(new Event('input', { bubbles: true })); + input.dispatchEvent(new Event('change', { bubbles: true })); + }, value); + } + + async function typeIntoElement(element, value) { + const textToType = await element.evaluate((input, newValue) => { + if ( + newValue.length <= input.value.length || + !newValue.startsWith(input.value) + ) { + input.value = ''; + return newValue; + } + const originalValue = input.value; + input.value = ''; + input.value = originalValue; + return newValue.substring(originalValue.length); + }, value); + await element.type(textToType); + } })().catch(err => { console.error(err); process.exit(1); diff --git a/__snapshots__/stringify.test.ts.js b/__snapshots__/stringify.test.ts.js index 7bc48b0f..c53f8841 100644 --- a/__snapshots__/stringify.test.ts.js +++ b/__snapshots__/stringify.test.ts.js @@ -163,6 +163,40 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later } throw new Error('Timed out'); } + + async function changeSelectElement(element, value) { + await element.select(value); + await element.evaluateHandle((e) => { + e.blur(); + e.focus(); + }); + } + + async function changeElementValue(element, value) { + await element.focus(); + await element.evaluate((input, value) => { + input.value = value; + input.dispatchEvent(new Event('input', { bubbles: true })); + input.dispatchEvent(new Event('change', { bubbles: true })); + }, value); + } + + async function typeIntoElement(element, value) { + const textToType = await element.evaluate((input, newValue) => { + if ( + newValue.length <= input.value.length || + !newValue.startsWith(input.value) + ) { + input.value = ''; + return newValue; + } + const originalValue = input.value; + input.value = ''; + input.value = originalValue; + return newValue.substring(originalValue.length); + }, value); + await element.type(textToType); + } })().catch(err => { console.error(err); process.exit(1); @@ -342,6 +376,40 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later } throw new Error('Timed out'); } + + async function changeSelectElement(element, value) { + await element.select(value); + await element.evaluateHandle((e) => { + e.blur(); + e.focus(); + }); + } + + async function changeElementValue(element, value) { + await element.focus(); + await element.evaluate((input, value) => { + input.value = value; + input.dispatchEvent(new Event('input', { bubbles: true })); + input.dispatchEvent(new Event('change', { bubbles: true })); + }, value); + } + + async function typeIntoElement(element, value) { + const textToType = await element.evaluate((input, newValue) => { + if ( + newValue.length <= input.value.length || + !newValue.startsWith(input.value) + ) { + input.value = ''; + return newValue; + } + const originalValue = input.value; + input.value = ''; + input.value = originalValue; + return newValue.substring(originalValue.length); + }, value); + await element.type(textToType); + } })().catch(err => { console.error(err); process.exit(1); @@ -525,6 +593,40 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later } throw new Error('Timed out'); } + + async function changeSelectElement(element, value) { + await element.select(value); + await element.evaluateHandle((e) => { + e.blur(); + e.focus(); + }); + } + + async function changeElementValue(element, value) { + await element.focus(); + await element.evaluate((input, value) => { + input.value = value; + input.dispatchEvent(new Event('input', { bubbles: true })); + input.dispatchEvent(new Event('change', { bubbles: true })); + }, value); + } + + async function typeIntoElement(element, value) { + const textToType = await element.evaluate((input, newValue) => { + if ( + newValue.length <= input.value.length || + !newValue.startsWith(input.value) + ) { + input.value = ''; + return newValue; + } + const originalValue = input.value; + input.value = ''; + input.value = originalValue; + return newValue.substring(originalValue.length); + }, value); + await element.type(textToType); + } })().catch(err => { console.error(err); process.exit(1); @@ -707,6 +809,40 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later } throw new Error('Timed out'); } + + async function changeSelectElement(element, value) { + await element.select(value); + await element.evaluateHandle((e) => { + e.blur(); + e.focus(); + }); + } + + async function changeElementValue(element, value) { + await element.focus(); + await element.evaluate((input, value) => { + input.value = value; + input.dispatchEvent(new Event('input', { bubbles: true })); + input.dispatchEvent(new Event('change', { bubbles: true })); + }, value); + } + + async function typeIntoElement(element, value) { + const textToType = await element.evaluate((input, newValue) => { + if ( + newValue.length <= input.value.length || + !newValue.startsWith(input.value) + ) { + input.value = ''; + return newValue; + } + const originalValue = input.value; + input.value = ''; + input.value = originalValue; + return newValue.substring(originalValue.length); + }, value); + await element.type(textToType); + } })().catch(err => { console.error(err); process.exit(1); @@ -891,6 +1027,40 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later } throw new Error('Timed out'); } + + async function changeSelectElement(element, value) { + await element.select(value); + await element.evaluateHandle((e) => { + e.blur(); + e.focus(); + }); + } + + async function changeElementValue(element, value) { + await element.focus(); + await element.evaluate((input, value) => { + input.value = value; + input.dispatchEvent(new Event('input', { bubbles: true })); + input.dispatchEvent(new Event('change', { bubbles: true })); + }, value); + } + + async function typeIntoElement(element, value) { + const textToType = await element.evaluate((input, newValue) => { + if ( + newValue.length <= input.value.length || + !newValue.startsWith(input.value) + ) { + input.value = ''; + return newValue; + } + const originalValue = input.value; + input.value = ''; + input.value = originalValue; + return newValue.substring(originalValue.length); + }, value); + await element.type(textToType); + } })().catch(err => { console.error(err); process.exit(1); @@ -1063,6 +1233,40 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later } throw new Error('Timed out'); } + + async function changeSelectElement(element, value) { + await element.select(value); + await element.evaluateHandle((e) => { + e.blur(); + e.focus(); + }); + } + + async function changeElementValue(element, value) { + await element.focus(); + await element.evaluate((input, value) => { + input.value = value; + input.dispatchEvent(new Event('input', { bubbles: true })); + input.dispatchEvent(new Event('change', { bubbles: true })); + }, value); + } + + async function typeIntoElement(element, value) { + const textToType = await element.evaluate((input, newValue) => { + if ( + newValue.length <= input.value.length || + !newValue.startsWith(input.value) + ) { + input.value = ''; + return newValue; + } + const originalValue = input.value; + input.value = ''; + input.value = originalValue; + return newValue.substring(originalValue.length); + }, value); + await element.type(textToType); + } })().catch(err => { console.error(err); process.exit(1); @@ -1235,6 +1439,40 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later } throw new Error('Timed out'); } + + async function changeSelectElement(element, value) { + await element.select(value); + await element.evaluateHandle((e) => { + e.blur(); + e.focus(); + }); + } + + async function changeElementValue(element, value) { + await element.focus(); + await element.evaluate((input, value) => { + input.value = value; + input.dispatchEvent(new Event('input', { bubbles: true })); + input.dispatchEvent(new Event('change', { bubbles: true })); + }, value); + } + + async function typeIntoElement(element, value) { + const textToType = await element.evaluate((input, newValue) => { + if ( + newValue.length <= input.value.length || + !newValue.startsWith(input.value) + ) { + input.value = ''; + return newValue; + } + const originalValue = input.value; + input.value = ''; + input.value = originalValue; + return newValue.substring(originalValue.length); + }, value); + await element.type(textToType); + } })().catch(err => { console.error(err); process.exit(1); @@ -1413,6 +1651,40 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later } throw new Error('Timed out'); } + + async function changeSelectElement(element, value) { + await element.select(value); + await element.evaluateHandle((e) => { + e.blur(); + e.focus(); + }); + } + + async function changeElementValue(element, value) { + await element.focus(); + await element.evaluate((input, value) => { + input.value = value; + input.dispatchEvent(new Event('input', { bubbles: true })); + input.dispatchEvent(new Event('change', { bubbles: true })); + }, value); + } + + async function typeIntoElement(element, value) { + const textToType = await element.evaluate((input, newValue) => { + if ( + newValue.length <= input.value.length || + !newValue.startsWith(input.value) + ) { + input.value = ''; + return newValue; + } + const originalValue = input.value; + input.value = ''; + input.value = originalValue; + return newValue.substring(originalValue.length); + }, value); + await element.type(textToType); + } })().catch(err => { console.error(err); process.exit(1); @@ -1587,6 +1859,40 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later } throw new Error('Timed out'); } + + async function changeSelectElement(element, value) { + await element.select(value); + await element.evaluateHandle((e) => { + e.blur(); + e.focus(); + }); + } + + async function changeElementValue(element, value) { + await element.focus(); + await element.evaluate((input, value) => { + input.value = value; + input.dispatchEvent(new Event('input', { bubbles: true })); + input.dispatchEvent(new Event('change', { bubbles: true })); + }, value); + } + + async function typeIntoElement(element, value) { + const textToType = await element.evaluate((input, newValue) => { + if ( + newValue.length <= input.value.length || + !newValue.startsWith(input.value) + ) { + input.value = ''; + return newValue; + } + const originalValue = input.value; + input.value = ''; + input.value = originalValue; + return newValue.substring(originalValue.length); + }, value); + await element.type(textToType); + } })().catch(err => { console.error(err); process.exit(1); @@ -1761,6 +2067,40 @@ const puppeteer = require('puppeteer'); // v13.0.0 or later } throw new Error('Timed out'); } + + async function changeSelectElement(element, value) { + await element.select(value); + await element.evaluateHandle((e) => { + e.blur(); + e.focus(); + }); + } + + async function changeElementValue(element, value) { + await element.focus(); + await element.evaluate((input, value) => { + input.value = value; + input.dispatchEvent(new Event('input', { bubbles: true })); + input.dispatchEvent(new Event('change', { bubbles: true })); + }, value); + } + + async function typeIntoElement(element, value) { + const textToType = await element.evaluate((input, newValue) => { + if ( + newValue.length <= input.value.length || + !newValue.startsWith(input.value) + ) { + input.value = ''; + return newValue; + } + const originalValue = input.value; + input.value = ''; + input.value = originalValue; + return newValue.substring(originalValue.length); + }, value); + await element.type(textToType); + } })().catch(err => { console.error(err); process.exit(1); diff --git a/src/PuppeteerStringifyExtension.ts b/src/PuppeteerStringifyExtension.ts index 9f93821a..83a91dfc 100644 --- a/src/PuppeteerStringifyExtension.ts +++ b/src/PuppeteerStringifyExtension.ts @@ -183,26 +183,23 @@ export class PuppeteerStringifyExtension extends StringifyExtension { #appendChangeStep(out: LineWriter, step: ChangeStep): void { this.#appendWaitForSelector(out, step); - out.appendLine('const type = await element.evaluate(el => el.type);'); - out.appendLine(`if (["select-one"].includes(type)) {`); - out.appendLine(` await element.select(${formatAsJSLiteral(step.value)});`); + out.appendLine('const inputType = await element.evaluate(el => el.type);'); + out.appendLine(`if (inputType === 'select-one') {`); + out.appendLine( + ` await changeSelectElement(element, ${formatAsJSLiteral(step.value)})` + ); out.appendLine( `} else if (${JSON.stringify( Array.from(typeableInputTypes) - )}.includes(type)) {` + )}.includes(inputType)) {` ); - out.appendLine(` await element.type(${formatAsJSLiteral(step.value)});`); - out.appendLine('} else {'); - out.appendLine(' await element.focus();'); - out.appendLine(' await element.evaluate((el, value) => {'); - out.appendLine(' el.value = value;'); out.appendLine( - " el.dispatchEvent(new Event('input', { bubbles: true }));" + ` await typeIntoElement(element, ${formatAsJSLiteral(step.value)});` ); + out.appendLine('} else {'); out.appendLine( - " el.dispatchEvent(new Event('change', { bubbles: true }));" + ` await changeElementValue(element, ${formatAsJSLiteral(step.value)});` ); - out.appendLine(` }, ${JSON.stringify(step.value)});`); out.appendLine('}'); } @@ -471,4 +468,38 @@ async function waitForFunction(fn, timeout) { await new Promise(resolve => setTimeout(resolve, 100)); } throw new Error('Timed out'); +} + +async function changeSelectElement(element, value) { + await element.select(value); + await element.evaluateHandle((e) => { + e.blur(); + e.focus(); + }); +} + +async function changeElementValue(element, value) { + await element.focus(); + await element.evaluate((input, value) => { + input.value = value; + input.dispatchEvent(new Event('input', { bubbles: true })); + input.dispatchEvent(new Event('change', { bubbles: true })); + }, value); +} + +async function typeIntoElement(element, value) { + const textToType = await element.evaluate((input, newValue) => { + if ( + newValue.length <= input.value.length || + !newValue.startsWith(input.value) + ) { + input.value = ''; + return newValue; + } + const originalValue = input.value; + input.value = ''; + input.value = originalValue; + return newValue.substring(originalValue.length); + }, value); + await element.type(textToType); }`; diff --git a/test/cli.test.ts b/test/cli.test.ts index 5ea41fd8..d6b941b7 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -78,7 +78,7 @@ describe('cli', () => { it('is able to run able to run folder of recordings', async () => { const recordings = getJSONFilesFromFolder( - path.join(__dirname, 'resources') + path.join(__dirname, 'resources', 'folder-test') ); const result = await getStatus(() => runFiles([...recordings])); assert.strictEqual(result, Status.Error); @@ -98,7 +98,7 @@ describe('cli', () => { describe('getRecordingPaths', () => { it('is able to get recordings from a directory', () => { - const recordingsFolderPath = 'test/resources'; + const recordingsFolderPath = 'test/resources/folder-test'; const recordingPaths = getRecordingPaths([recordingsFolderPath]); assert.isTrue( @@ -112,7 +112,9 @@ describe('cli', () => { describe('getJSONFilesFromFolder', () => { it('is able to return json files from a directory', () => { - const files = getJSONFilesFromFolder(path.join(__dirname, 'resources')); + const files = getJSONFilesFromFolder( + path.join(__dirname, 'resources', 'folder-test') + ); assert.isTrue(files.every((file) => file.endsWith('.json'))); }); diff --git a/test/everything.test.ts b/test/everything.test.ts index b016ece9..d52ac639 100644 --- a/test/everything.test.ts +++ b/test/everything.test.ts @@ -49,8 +49,10 @@ click targetId=button button=0 value= click targetId=button button=0 value= dblclick targetId=button button=0 value= change targetId=input button=undefined value=test +change targetId=input-prefilled button=undefined value=testSuffix contextmenu targetId=input button=2 value=test -mouseenter targetId=hover button=0 value=`; +mouseenter targetId=hover button=0 value= +change targetId=select button=undefined value=optionB`; describe('Everything', () => { let browser: puppeteer.Browser; diff --git a/test/resources/everything.html b/test/resources/everything.html index 77054d7d..4bac5939 100644 --- a/test/resources/everything.html +++ b/test/resources/everything.html @@ -10,6 +10,12 @@ Hover + +