test: migrate cli tests to new folio (#6048)

This commit is contained in:
Dmitry Gozman 2021-04-02 11:19:26 -07:00 committed by GitHub
parent 561cb23e8d
commit d0afa9d8de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 396 additions and 297 deletions

View file

@ -14,16 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */
import { folio } from './cli.fixtures'; import { test, expect } from '../config/cliTest';
import * as http from 'http'; import * as http from 'http';
const { it, describe, expect } = folio; test.describe('cli codegen', () => {
test.beforeEach(async ({ mode, browserName, headful }) => {
test.skip(mode !== 'default');
test.fixme(browserName === 'firefox' && headful, 'Focus is off');
});
test('should click', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
describe('cli codegen', (suite, { browserName, headful, mode }) => {
suite.skip(mode !== 'default');
suite.fixme(browserName === 'firefox' && headful, 'Focus is off');
}, () => {
it('should click', async ({ page, recorder }) => {
await recorder.setContentAndWait(`<button onclick="console.log('click')">Submit</button>`); await recorder.setContentAndWait(`<button onclick="console.log('click')">Submit</button>`);
const selector = await recorder.hoverOverElement('button'); const selector = await recorder.hoverOverElement('button');
@ -58,7 +60,9 @@ await page.ClickAsync("text=Submit");`);
expect(message.text()).toBe('click'); expect(message.text()).toBe('click');
}); });
it('should click after same-document navigation', async ({ page, recorder, httpServer }) => { test('should click after same-document navigation', async ({ page, openRecorder, httpServer }) => {
const recorder = await openRecorder();
httpServer.setHandler((req: http.IncomingMessage, res: http.ServerResponse) => { httpServer.setHandler((req: http.IncomingMessage, res: http.ServerResponse) => {
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end(''); res.end('');
@ -87,7 +91,9 @@ await page.ClickAsync("text=Submit");`);
expect(message.text()).toBe('click'); expect(message.text()).toBe('click');
}); });
it('should work with TrustedTypes', async ({ page, recorder }) => { test('should work with TrustedTypes', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(` await recorder.setContentAndWait(`
<head> <head>
<meta http-equiv="Content-Security-Policy" content="trusted-types unsafe escape; require-trusted-types-for 'script'"> <meta http-equiv="Content-Security-Policy" content="trusted-types unsafe escape; require-trusted-types-for 'script'">
@ -128,7 +134,9 @@ await page.ClickAsync("text=Submit");`);
expect(message.text()).toBe('click'); expect(message.text()).toBe('click');
}); });
it('should not target selector preview by text regexp', async ({ page, recorder }) => { test('should not target selector preview by text regexp', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<span>dummy</span>`); await recorder.setContentAndWait(`<span>dummy</span>`);
// Force highlight. // Force highlight.
@ -160,7 +168,9 @@ await page.ClickAsync("text=Submit");`);
expect(message.text()).toBe('click'); expect(message.text()).toBe('click');
}); });
it('should fill', async ({ page, recorder }) => { test('should fill', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<input id="input" name="name" oninput="console.log(input.value)"></input>`); await recorder.setContentAndWait(`<input id="input" name="name" oninput="console.log(input.value)"></input>`);
const selector = await recorder.focusElement('input'); const selector = await recorder.focusElement('input');
expect(selector).toBe('input[name="name"]'); expect(selector).toBe('input[name="name"]');
@ -193,7 +203,9 @@ await page.FillAsync(\"input[name=\\\"name\\\"]\", \"John\");`);
expect(message.text()).toBe('John'); expect(message.text()).toBe('John');
}); });
it('should fill textarea', async ({ page, recorder }) => { test('should fill textarea', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<textarea id="textarea" name="name" oninput="console.log(textarea.value)"></textarea>`); await recorder.setContentAndWait(`<textarea id="textarea" name="name" oninput="console.log(textarea.value)"></textarea>`);
const selector = await recorder.focusElement('textarea'); const selector = await recorder.focusElement('textarea');
expect(selector).toBe('textarea[name="name"]'); expect(selector).toBe('textarea[name="name"]');
@ -209,7 +221,9 @@ await page.FillAsync(\"input[name=\\\"name\\\"]\", \"John\");`);
expect(message.text()).toBe('John'); expect(message.text()).toBe('John');
}); });
it('should press', async ({ page, recorder }) => { test('should press', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<input name="name" onkeypress="console.log('press')"></input>`); await recorder.setContentAndWait(`<input name="name" onkeypress="console.log('press')"></input>`);
const selector = await recorder.focusElement('input'); const selector = await recorder.focusElement('input');
@ -246,7 +260,9 @@ await page.PressAsync(\"input[name=\\\"name\\\"]\", \"Shift+Enter\");`);
expect(messages[0].text()).toBe('press'); expect(messages[0].text()).toBe('press');
}); });
it('should update selected element after pressing Tab', async ({ page, recorder }) => { test('should update selected element after pressing Tab', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(` await recorder.setContentAndWait(`
<input name="one"></input> <input name="one"></input>
<input name="two"></input> <input name="two"></input>
@ -276,7 +292,9 @@ await page.PressAsync(\"input[name=\\\"name\\\"]\", \"Shift+Enter\");`);
await page.fill('input[name="two"]', 'barfoo321');`); await page.fill('input[name="two"]', 'barfoo321');`);
}); });
it('should record ArrowDown', async ({ page, recorder }) => { test('should record ArrowDown', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<input name="name" onkeydown="console.log('press:' + event.key)"></input>`); await recorder.setContentAndWait(`<input name="name" onkeydown="console.log('press:' + event.key)"></input>`);
const selector = await recorder.focusElement('input'); const selector = await recorder.focusElement('input');
@ -297,7 +315,9 @@ await page.PressAsync(\"input[name=\\\"name\\\"]\", \"Shift+Enter\");`);
expect(messages[0].text()).toBe('press:ArrowDown'); expect(messages[0].text()).toBe('press:ArrowDown');
}); });
it('should emit single keyup on ArrowDown', async ({ page, recorder }) => { test('should emit single keyup on ArrowDown', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<input name="name" onkeydown="console.log('down:' + event.key)" onkeyup="console.log('up:' + event.key)"></input>`); await recorder.setContentAndWait(`<input name="name" onkeydown="console.log('down:' + event.key)" onkeyup="console.log('up:' + event.key)"></input>`);
const selector = await recorder.focusElement('input'); const selector = await recorder.focusElement('input');
@ -320,7 +340,9 @@ await page.PressAsync(\"input[name=\\\"name\\\"]\", \"Shift+Enter\");`);
expect(messages[1].text()).toBe('up:ArrowDown'); expect(messages[1].text()).toBe('up:ArrowDown');
}); });
it('should check', async ({ page, recorder }) => { test('should check', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="console.log(checkbox.checked)"></input>`); await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="console.log(checkbox.checked)"></input>`);
const selector = await recorder.focusElement('input'); const selector = await recorder.focusElement('input');
@ -355,7 +377,9 @@ await page.CheckAsync(\"input[name=\\\"accept\\\"]\");`);
expect(message.text()).toBe('true'); expect(message.text()).toBe('true');
}); });
it('should check with keyboard', async ({ page, recorder }) => { test('should check with keyboard', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="console.log(checkbox.checked)"></input>`); await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="console.log(checkbox.checked)"></input>`);
const selector = await recorder.focusElement('input'); const selector = await recorder.focusElement('input');
@ -373,7 +397,9 @@ await page.CheckAsync(\"input[name=\\\"accept\\\"]\");`);
expect(message.text()).toBe('true'); expect(message.text()).toBe('true');
}); });
it('should uncheck', async ({ page, recorder }) => { test('should uncheck', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" checked name="accept" onchange="console.log(checkbox.checked)"></input>`); await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" checked name="accept" onchange="console.log(checkbox.checked)"></input>`);
const selector = await recorder.focusElement('input'); const selector = await recorder.focusElement('input');
@ -408,7 +434,9 @@ await page.UncheckAsync(\"input[name=\\\"accept\\\"]\");`);
expect(message.text()).toBe('false'); expect(message.text()).toBe('false');
}); });
it('should select', async ({ page, recorder }) => { test('should select', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait('<select id="age" onchange="console.log(age.selectedOptions[0].value)"><option value="1"><option value="2"></select>'); await recorder.setContentAndWait('<select id="age" onchange="console.log(age.selectedOptions[0].value)"><option value="1"><option value="2"></select>');
const selector = await recorder.hoverOverElement('select'); const selector = await recorder.hoverOverElement('select');
@ -443,9 +471,10 @@ await page.SelectOptionAsync(\"select\", \"2\");`);
expect(message.text()).toBe('2'); expect(message.text()).toBe('2');
}); });
it('should await popup', (test, { browserName, headful }) => { test('should await popup', async ({ page, openRecorder, browserName, headful }) => {
test.fixme(browserName === 'webkit' && headful, 'Middle click does not open a popup in our webkit embedder'); test.fixme(browserName === 'webkit' && headful, 'Middle click does not open a popup in our webkit embedder');
}, async ({ page, recorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait('<a target=_blank rel=noopener href="about:blank">link</a>'); await recorder.setContentAndWait('<a target=_blank rel=noopener href="about:blank">link</a>');
const selector = await recorder.hoverOverElement('a'); const selector = await recorder.hoverOverElement('a');
@ -491,7 +520,9 @@ await Task.WhenAll(
expect(popup.url()).toBe('about:blank'); expect(popup.url()).toBe('about:blank');
}); });
it('should assert navigation', async ({ page, recorder }) => { test('should assert navigation', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<a onclick="window.location.href='about:blank#foo'">link</a>`); await recorder.setContentAndWait(`<a onclick="window.location.href='about:blank#foo'">link</a>`);
const selector = await recorder.hoverOverElement('a'); const selector = await recorder.hoverOverElement('a');
@ -531,7 +562,9 @@ await page.ClickAsync(\"text=link\");
}); });
it('should await navigation', async ({ page, recorder }) => { test('should await navigation', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<a onclick="setTimeout(() => window.location.href='about:blank#foo', 1000)">link</a>`); await recorder.setContentAndWait(`<a onclick="setTimeout(() => window.location.href='about:blank#foo', 1000)">link</a>`);
const selector = await recorder.hoverOverElement('a'); const selector = await recorder.hoverOverElement('a');

View file

@ -14,16 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */
import { folio } from './cli.fixtures'; import { test, expect } from '../config/cliTest';
import * as http from 'http'; import * as http from 'http';
import * as url from 'url'; import * as url from 'url';
const { it, describe, expect } = folio; test.describe('cli codegen', () => {
test.beforeEach(async ({ mode }) => {
test.skip(mode !== 'default');
});
test('should contain open page', async ({ openRecorder }) => {
const recorder = await openRecorder();
describe('cli codegen', (suite, { mode }) => {
suite.skip(mode !== 'default');
}, () => {
it('should contain open page', async ({ recorder }) => {
await recorder.setContentAndWait(``); await recorder.setContentAndWait(``);
const sources = await recorder.waitForOutput('<javascript>', `page.goto`); const sources = await recorder.waitForOutput('<javascript>', `page.goto`);
@ -48,9 +50,11 @@ describe('cli codegen', (suite, { mode }) => {
var page = await context.NewPageAsync();`); var page = await context.NewPageAsync();`);
}); });
it('should contain second page', async ({ context, recorder }) => { test('should contain second page', async ({ openRecorder, page }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(``); await recorder.setContentAndWait(``);
await context.newPage(); await page.context().newPage();
const sources = await recorder.waitForOutput('<javascript>', 'page1'); const sources = await recorder.waitForOutput('<javascript>', 'page1');
expect(sources.get('<javascript>').text).toContain(` expect(sources.get('<javascript>').text).toContain(`
@ -74,9 +78,11 @@ var page = await context.NewPageAsync();`);
var page1 = await context.NewPageAsync();`); var page1 = await context.NewPageAsync();`);
}); });
it('should contain close page', async ({ context, recorder }) => { test('should contain close page', async ({ openRecorder, page }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(``); await recorder.setContentAndWait(``);
await context.newPage(); await page.context().newPage();
await recorder.page.close(); await recorder.page.close();
const sources = await recorder.waitForOutput('<javascript>', 'page.close();'); const sources = await recorder.waitForOutput('<javascript>', 'page.close();');
@ -96,9 +102,11 @@ var page1 = await context.NewPageAsync();`);
await page.CloseAsync();`); await page.CloseAsync();`);
}); });
it('should not lead to an error if html gets clicked', async ({ context, recorder }) => { test('should not lead to an error if html gets clicked', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(''); await recorder.setContentAndWait('');
await context.newPage(); await page.context().newPage();
const errors: any[] = []; const errors: any[] = [];
recorder.page.on('pageerror', e => errors.push(e)); recorder.page.on('pageerror', e => errors.push(e));
await recorder.page.evaluate(() => document.querySelector('body').remove()); await recorder.page.evaluate(() => document.querySelector('body').remove());
@ -109,9 +117,10 @@ await page.CloseAsync();`);
expect(errors.length).toBe(0); expect(errors.length).toBe(0);
}); });
it('should upload a single file', (test, { browserName }) => { test('should upload a single file', async ({ page, openRecorder, browserName }) => {
test.fixme(browserName === 'firefox', 'Hangs'); test.fixme(browserName === 'firefox', 'Hangs');
}, async ({ page, recorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(` await recorder.setContentAndWait(`
<form> <form>
<input type="file"> <input type="file">
@ -146,9 +155,10 @@ await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", \"file-to-upload.txt
}); });
it('should upload multiple files', (test, { browserName }) => { test('should upload multiple files', async ({ page, openRecorder, browserName }) => {
test.fixme(browserName === 'firefox', 'Hangs'); test.fixme(browserName === 'firefox', 'Hangs');
}, async ({ page, recorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(` await recorder.setContentAndWait(`
<form> <form>
<input type="file" multiple> <input type="file" multiple>
@ -182,9 +192,10 @@ await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", \"file-to-upload.txt
await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { \"file-to-upload.txt\", \"file-to-upload-2.txt\" });`); await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { \"file-to-upload.txt\", \"file-to-upload-2.txt\" });`);
}); });
it('should clear files', (test, { browserName }) => { test('should clear files', async ({ page, openRecorder, browserName }) => {
test.fixme(browserName === 'firefox', 'Hangs'); test.fixme(browserName === 'firefox', 'Hangs');
}, async ({ page, recorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(` await recorder.setContentAndWait(`
<form> <form>
<input type="file" multiple> <input type="file" multiple>
@ -219,7 +230,9 @@ await page.SetInputFilesAsync(\"input[type=\\\"file\\\"]\", new[] { });`);
}); });
it('should download files', async ({ page, recorder, httpServer }) => { test('should download files', async ({ page, openRecorder, httpServer }) => {
const recorder = await openRecorder();
httpServer.setHandler((req: http.IncomingMessage, res: http.ServerResponse) => { httpServer.setHandler((req: http.IncomingMessage, res: http.ServerResponse) => {
const pathName = url.parse(req.url!).path; const pathName = url.parse(req.url!).path;
if (pathName === '/download') { if (pathName === '/download') {
@ -274,7 +287,9 @@ await Task.WhenAll(
page.ClickAsync(\"text=Download\"));`); page.ClickAsync(\"text=Download\"));`);
}); });
it('should handle dialogs', async ({ page, recorder }) => { test('should handle dialogs', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(` await recorder.setContentAndWait(`
<button onclick="alert()">click me</button> <button onclick="alert()">click me</button>
`); `);
@ -325,7 +340,9 @@ await page.ClickAsync(\"text=click me\");`);
}); });
it('should handle history.postData', async ({ page, recorder, httpServer }) => { test('should handle history.postData', async ({ page, openRecorder, httpServer }) => {
const recorder = await openRecorder();
httpServer.setHandler((req: http.IncomingMessage, res: http.ServerResponse) => { httpServer.setHandler((req: http.IncomingMessage, res: http.ServerResponse) => {
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end('Hello world'); res.end('Hello world');
@ -343,9 +360,10 @@ await page.ClickAsync(\"text=click me\");`);
} }
}); });
it('should record open in a new tab with url', (test, { browserName }) => { test('should record open in a new tab with url', async ({ page, openRecorder, browserName, platform }) => {
test.fixme(browserName === 'webkit', 'Ctrl+click does not open in new tab on WebKit'); test.fixme(browserName === 'webkit', 'Ctrl+click does not open in new tab on WebKit');
}, async ({ page, recorder, browserName, platform }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<a href="about:blank?foo">link</a>`); await recorder.setContentAndWait(`<a href="about:blank?foo">link</a>`);
const selector = await recorder.hoverOverElement('a'); const selector = await recorder.hoverOverElement('a');
@ -371,9 +389,10 @@ await page.ClickAsync(\"text=click me\");`);
} }
}); });
it('should not clash pages', (test, { browserName }) => { test('should not clash pages', async ({ page, openRecorder, browserName }) => {
test.fixme(browserName === 'firefox', 'Times out on Firefox, maybe the focus issue'); test.fixme(browserName === 'firefox', 'Times out on Firefox, maybe the focus issue');
}, async ({ page, recorder }) => {
const recorder = await openRecorder();
const [popup1] = await Promise.all([ const [popup1] = await Promise.all([
page.context().waitForEvent('page'), page.context().waitForEvent('page'),
page.evaluate(`window.open('about:blank')`) page.evaluate(`window.open('about:blank')`)
@ -409,7 +428,9 @@ await page.ClickAsync(\"text=click me\");`);
expect(sources.get('<csharp>').text).toContain(`await page2.FillAsync(\"input\", \"TextB\");`); expect(sources.get('<csharp>').text).toContain(`await page2.FillAsync(\"input\", \"TextB\");`);
}); });
it('click should emit events in order', async ({ page, recorder }) => { test('click should emit events in order', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(` await recorder.setContentAndWait(`
<button id=button> <button id=button>
<script> <script>
@ -428,7 +449,9 @@ await page.ClickAsync(\"text=click me\");`);
expect(messages).toEqual(['mousedown', 'mouseup', 'click']); expect(messages).toEqual(['mousedown', 'mouseup', 'click']);
}); });
it('should update hover model on action', async ({ page, recorder }) => { test('should update hover model on action', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="checkbox.name='updated'"></input>`); await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="checkbox.name='updated'"></input>`);
const [ models ] = await Promise.all([ const [ models ] = await Promise.all([
recorder.waitForActionPerformed(), recorder.waitForActionPerformed(),
@ -437,10 +460,11 @@ await page.ClickAsync(\"text=click me\");`);
expect(models.hovered).toBe('input[name="updated"]'); expect(models.hovered).toBe('input[name="updated"]');
}); });
it('should update active model on action', (test, { browserName, headful }) => { test('should update active model on action', async ({ page, openRecorder, browserName, headful }) => {
test.fixme(browserName === 'webkit' && !headful); test.fixme(browserName === 'webkit' && !headful);
test.fixme(browserName === 'firefox' && !headful); test.fixme(browserName === 'firefox' && !headful);
}, async ({ page, recorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="checkbox.name='updated'"></input>`); await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="checkbox.name='updated'"></input>`);
const [ models ] = await Promise.all([ const [ models ] = await Promise.all([
recorder.waitForActionPerformed(), recorder.waitForActionPerformed(),
@ -449,7 +473,8 @@ await page.ClickAsync(\"text=click me\");`);
expect(models.active).toBe('input[name="updated"]'); expect(models.active).toBe('input[name="updated"]');
}); });
it('should check input with chaning id', async ({ page, recorder }) => { test('should check input with chaning id', async ({ page, openRecorder }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="checkbox.name = 'updated'"></input>`); await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="checkbox.name = 'updated'"></input>`);
await Promise.all([ await Promise.all([
recorder.waitForActionPerformed(), recorder.waitForActionPerformed(),
@ -457,7 +482,8 @@ await page.ClickAsync(\"text=click me\");`);
]); ]);
}); });
it('should prefer frame name', async ({ page, recorder, server }) => { test('should prefer frame name', async ({ page, openRecorder, server }) => {
const recorder = await openRecorder();
await recorder.setContentAndWait(` await recorder.setContentAndWait(`
<iframe src='./frames/frame.html' name='one'></iframe> <iframe src='./frames/frame.html' name='one'></iframe>
<iframe src='./frames/frame.html' name='two'></iframe> <iframe src='./frames/frame.html' name='two'></iframe>
@ -549,7 +575,8 @@ await page.GetFrame(name: \"two\").ClickAsync(\"text=Hi, I'm frame\");`);
await page.GetFrame(url: \"http://localhost:${server.PORT}/frames/frame.html\").ClickAsync(\"text=Hi, I'm frame\");`); await page.GetFrame(url: \"http://localhost:${server.PORT}/frames/frame.html\").ClickAsync(\"text=Hi, I'm frame\");`);
}); });
it('should record navigations after identical pushState', async ({ page, recorder, httpServer }) => { test('should record navigations after identical pushState', async ({ page, openRecorder, httpServer }) => {
const recorder = await openRecorder();
httpServer.setHandler((req: http.IncomingMessage, res: http.ServerResponse) => { httpServer.setHandler((req: http.IncomingMessage, res: http.ServerResponse) => {
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end('Hello world'); res.end('Hello world');

View file

@ -16,11 +16,9 @@
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import { folio } from './cli.fixtures'; import { test, expect } from '../config/cliTest';
const { it, expect } = folio; const emptyHTML = new URL('file://' + path.join(__dirname, '..', '..', 'test', 'assets', 'empty.html')).toString();
const emptyHTML = new URL('file://' + path.join(__dirname, '..', 'assets', 'empty.html')).toString();
const launchOptions = (channel: string) => { const launchOptions = (channel: string) => {
return channel ? `headless: false,\n channel: "${channel}"` : 'headless: false'; return channel ? `headless: false,\n channel: "${channel}"` : 'headless: false';
}; };
@ -29,7 +27,7 @@ function capitalize(browserName: string): string {
return browserName[0].toUpperCase() + browserName.slice(1); return browserName[0].toUpperCase() + browserName.slice(1);
} }
it('should print the correct imports and context options', async ({ browserName, browserChannel, runCLI }) => { test('should print the correct imports and context options', async ({ browserName, browserChannel, runCLI }) => {
const cli = runCLI(['--target=csharp', emptyHTML]); const cli = runCLI(['--target=csharp', emptyHTML]);
const expectedResult = `await Playwright.InstallAsync(); const expectedResult = `await Playwright.InstallAsync();
using var playwright = await Playwright.CreateAsync(); using var playwright = await Playwright.CreateAsync();
@ -41,7 +39,7 @@ var context = await browser.NewContextAsync();`;
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should print the correct context options for custom settings', async ({ browserName, browserChannel, runCLI }) => { test('should print the correct context options for custom settings', async ({ browserName, browserChannel, runCLI }) => {
const cli = runCLI([ const cli = runCLI([
'--color-scheme=dark', '--color-scheme=dark',
'--geolocation=37.819722,-122.478611', '--geolocation=37.819722,-122.478611',
@ -81,9 +79,9 @@ var context = await browser.NewContextAsync(
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should print the correct context options when using a device', (test, { browserName }) => { test('should print the correct context options when using a device', async ({ browserName, browserChannel, runCLI }) => {
test.skip(browserName !== 'chromium'); test.skip(browserName !== 'chromium');
}, async ({ browserChannel, runCLI }) => {
const cli = runCLI(['--device=Pixel 2', '--target=csharp', emptyHTML]); const cli = runCLI(['--device=Pixel 2', '--target=csharp', emptyHTML]);
const expectedResult = `await Playwright.InstallAsync(); const expectedResult = `await Playwright.InstallAsync();
using var playwright = await Playwright.CreateAsync(); using var playwright = await Playwright.CreateAsync();
@ -95,9 +93,9 @@ var context = await browser.NewContextAsync(playwright.Devices["Pixel 2"]);`;
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should print the correct context options when using a device and additional options', (test, { browserName }) => { test('should print the correct context options when using a device and additional options', async ({ browserName, browserChannel, runCLI }) => {
test.skip(browserName !== 'webkit'); test.skip(browserName !== 'webkit');
}, async ({ browserChannel, runCLI }) => {
const cli = runCLI([ const cli = runCLI([
'--device=iPhone 11', '--device=iPhone 11',
'--color-scheme=dark', '--color-scheme=dark',
@ -141,7 +139,7 @@ var context = await browser.NewContextAsync(new BrowserContextOptions(playwright
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should print load/save storageState', async ({ browserName, browserChannel, runCLI, testInfo }) => { test('should print load/save storageState', async ({ browserName, browserChannel, runCLI }, testInfo) => {
const loadFileName = testInfo.outputPath('load.json'); const loadFileName = testInfo.outputPath('load.json');
const saveFileName = testInfo.outputPath('save.json'); const saveFileName = testInfo.outputPath('save.json');
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8'); await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');

View file

@ -16,16 +16,14 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { folio } from './cli.fixtures'; import { test, expect } from '../config/cliTest';
const { it, expect } = folio; const emptyHTML = new URL('file://' + path.join(__dirname, '..', '..', 'test', 'assets', 'empty.html')).toString();
const emptyHTML = new URL('file://' + path.join(__dirname, '..', 'assets', 'empty.html')).toString();
const launchOptions = (channel: string) => { const launchOptions = (channel: string) => {
return channel ? `.setHeadless(false)\n .setChannel("${channel}")` : '.setHeadless(false)'; return channel ? `.setHeadless(false)\n .setChannel("${channel}")` : '.setHeadless(false)';
}; };
it('should print the correct imports and context options', async ({ runCLI, browserChannel, browserName }) => { test('should print the correct imports and context options', async ({ runCLI, browserChannel, browserName }) => {
const cli = runCLI(['--target=java', emptyHTML]); const cli = runCLI(['--target=java', emptyHTML]);
const expectedResult = `import com.microsoft.playwright.*; const expectedResult = `import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*; import com.microsoft.playwright.options.*;
@ -40,7 +38,7 @@ public class Example {
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should print the correct context options for custom settings', async ({ runCLI, browserName }) => { test('should print the correct context options for custom settings', async ({ runCLI, browserName }) => {
const cli = runCLI(['--color-scheme=light', '--target=java', emptyHTML]); const cli = runCLI(['--color-scheme=light', '--target=java', emptyHTML]);
const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions() const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setColorScheme(ColorScheme.LIGHT));`; .setColorScheme(ColorScheme.LIGHT));`;
@ -48,9 +46,9 @@ it('should print the correct context options for custom settings', async ({ runC
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should print the correct context options when using a device', (test, { browserName }) => { test('should print the correct context options when using a device', async ({ browserName, runCLI }) => {
test.skip(browserName !== 'chromium'); test.skip(browserName !== 'chromium');
}, async ({ runCLI }) => {
const cli = runCLI(['--device=Pixel 2', '--target=java', emptyHTML]); const cli = runCLI(['--device=Pixel 2', '--target=java', emptyHTML]);
const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions() const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setUserAgent("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36") .setUserAgent("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36")
@ -62,9 +60,9 @@ it('should print the correct context options when using a device', (test, { brow
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should print the correct context options when using a device and additional options', (test, { browserName }) => { test('should print the correct context options when using a device and additional options', async ({ browserName, runCLI }) => {
test.skip(browserName !== 'webkit'); test.skip(browserName !== 'webkit');
}, async ({ runCLI }) => {
const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', '--target=java', emptyHTML]); const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', '--target=java', emptyHTML]);
const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions() const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setColorScheme(ColorScheme.LIGHT) .setColorScheme(ColorScheme.LIGHT)
@ -77,7 +75,7 @@ it('should print the correct context options when using a device and additional
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should print load/save storage_state', async ({ runCLI, browserName, testInfo }) => { test('should print load/save storage_state', async ({ runCLI, browserName }, testInfo) => {
const loadFileName = testInfo.outputPath('load.json'); const loadFileName = testInfo.outputPath('load.json');
const saveFileName = testInfo.outputPath('save.json'); const saveFileName = testInfo.outputPath('save.json');
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8'); await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');

View file

@ -16,17 +16,15 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { folio } from './cli.fixtures'; import { test, expect } from '../config/cliTest';
const { it, expect } = folio; const emptyHTML = new URL('file://' + path.join(__dirname, '..', '..', 'test', 'assets', 'empty.html')).toString();
const emptyHTML = new URL('file://' + path.join(__dirname, '..', 'assets', 'empty.html')).toString();
const launchOptions = (channel: string) => { const launchOptions = (channel: string) => {
return channel ? `headless: false,\n channel: '${channel}'` : 'headless: false'; return channel ? `headless: false,\n channel: '${channel}'` : 'headless: false';
}; };
it('should print the correct imports and context options', async ({ browserName, browserChannel, runCLI }) => { test('should print the correct imports and context options', async ({ browserName, browserChannel, runCLI }) => {
const cli = runCLI([emptyHTML]); const cli = runCLI([emptyHTML]);
const expectedResult = `const { ${browserName} } = require('playwright'); const expectedResult = `const { ${browserName} } = require('playwright');
@ -39,7 +37,7 @@ it('should print the correct imports and context options', async ({ browserName,
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should print the correct context options for custom settings', async ({ browserName, browserChannel, runCLI }) => { test('should print the correct context options for custom settings', async ({ browserName, browserChannel, runCLI }) => {
const cli = runCLI(['--color-scheme=light', emptyHTML]); const cli = runCLI(['--color-scheme=light', emptyHTML]);
const expectedResult = `const { ${browserName} } = require('playwright'); const expectedResult = `const { ${browserName} } = require('playwright');
@ -55,9 +53,9 @@ it('should print the correct context options for custom settings', async ({ brow
}); });
it('should print the correct context options when using a device', (test, { browserName, browserChannel }) => { test('should print the correct context options when using a device', async ({ browserName, browserChannel, runCLI }) => {
test.skip(browserName !== 'chromium'); test.skip(browserName !== 'chromium');
}, async ({ browserChannel, runCLI }) => {
const cli = runCLI(['--device=Pixel 2', emptyHTML]); const cli = runCLI(['--device=Pixel 2', emptyHTML]);
const expectedResult = `const { chromium, devices } = require('playwright'); const expectedResult = `const { chromium, devices } = require('playwright');
@ -72,9 +70,9 @@ it('should print the correct context options when using a device', (test, { brow
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should print the correct context options when using a device and additional options', (test, { browserName }) => { test('should print the correct context options when using a device and additional options', async ({ browserName, browserChannel, runCLI }) => {
test.skip(browserName !== 'webkit'); test.skip(browserName !== 'webkit');
}, async ({ browserChannel, runCLI }) => {
const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', emptyHTML]); const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', emptyHTML]);
const expectedResult = `const { webkit, devices } = require('playwright'); const expectedResult = `const { webkit, devices } = require('playwright');
@ -90,7 +88,7 @@ it('should print the correct context options when using a device and additional
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should save the codegen output to a file if specified', async ({ browserName, browserChannel, runCLI, testInfo }) => { test('should save the codegen output to a file if specified', async ({ browserName, browserChannel, runCLI }, testInfo) => {
const tmpFile = testInfo.outputPath('script.js'); const tmpFile = testInfo.outputPath('script.js');
const cli = runCLI(['--output', tmpFile, emptyHTML]); const cli = runCLI(['--output', tmpFile, emptyHTML]);
await cli.exited; await cli.exited;
@ -118,7 +116,7 @@ it('should save the codegen output to a file if specified', async ({ browserName
})();`); })();`);
}); });
it('should print load/save storageState', async ({ browserName, browserChannel, runCLI, testInfo }) => { test('should print load/save storageState', async ({ browserName, browserChannel, runCLI }, testInfo) => {
const loadFileName = testInfo.outputPath('load.json'); const loadFileName = testInfo.outputPath('load.json');
const saveFileName = testInfo.outputPath('save.json'); const saveFileName = testInfo.outputPath('save.json');
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8'); await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');

View file

@ -16,16 +16,14 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { folio } from './cli.fixtures'; import { test, expect } from '../config/cliTest';
const { it, expect } = folio; const emptyHTML = new URL('file://' + path.join(__dirname, '..', '..', 'test', 'assets', 'empty.html')).toString();
const emptyHTML = new URL('file://' + path.join(__dirname, '..', 'assets', 'empty.html')).toString();
const launchOptions = (channel: string) => { const launchOptions = (channel: string) => {
return channel ? `headless=False, channel="${channel}"` : 'headless=False'; return channel ? `headless=False, channel="${channel}"` : 'headless=False';
}; };
it('should print the correct imports and context options', async ({ browserName, browserChannel, runCLI }) => { test('should print the correct imports and context options', async ({ browserName, browserChannel, runCLI }) => {
const cli = runCLI(['--target=python-async', emptyHTML]); const cli = runCLI(['--target=python-async', emptyHTML]);
const expectedResult = `import asyncio const expectedResult = `import asyncio
from playwright.async_api import async_playwright from playwright.async_api import async_playwright
@ -37,7 +35,7 @@ async def run(playwright):
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should print the correct context options for custom settings', async ({ browserName, browserChannel, runCLI }) => { test('should print the correct context options for custom settings', async ({ browserName, browserChannel, runCLI }) => {
const cli = runCLI(['--color-scheme=light', '--target=python-async', emptyHTML]); const cli = runCLI(['--color-scheme=light', '--target=python-async', emptyHTML]);
const expectedResult = `import asyncio const expectedResult = `import asyncio
from playwright.async_api import async_playwright from playwright.async_api import async_playwright
@ -49,9 +47,9 @@ async def run(playwright):
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should print the correct context options when using a device', (test, { browserName }) => { test('should print the correct context options when using a device', async ({ browserName, browserChannel, runCLI }) => {
test.skip(browserName !== 'chromium'); test.skip(browserName !== 'chromium');
}, async ({ browserChannel, runCLI }) => {
const cli = runCLI(['--device=Pixel 2', '--target=python-async', emptyHTML]); const cli = runCLI(['--device=Pixel 2', '--target=python-async', emptyHTML]);
const expectedResult = `import asyncio const expectedResult = `import asyncio
from playwright.async_api import async_playwright from playwright.async_api import async_playwright
@ -63,9 +61,9 @@ async def run(playwright):
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should print the correct context options when using a device and additional options', (test, { browserName }) => { test('should print the correct context options when using a device and additional options', async ({ browserName, browserChannel, runCLI }) => {
test.skip(browserName !== 'webkit'); test.skip(browserName !== 'webkit');
}, async ({ browserChannel, runCLI }) => {
const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', '--target=python-async', emptyHTML]); const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', '--target=python-async', emptyHTML]);
const expectedResult = `import asyncio const expectedResult = `import asyncio
from playwright.async_api import async_playwright from playwright.async_api import async_playwright
@ -77,7 +75,7 @@ async def run(playwright):
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should save the codegen output to a file if specified', async ({ browserName, browserChannel, runCLI, testInfo }) => { test('should save the codegen output to a file if specified', async ({ browserName, browserChannel, runCLI }, testInfo) => {
const tmpFile = testInfo.outputPath('script.js'); const tmpFile = testInfo.outputPath('script.js');
const cli = runCLI(['--target=python-async', '--output', tmpFile, emptyHTML]); const cli = runCLI(['--target=python-async', '--output', tmpFile, emptyHTML]);
await cli.exited; await cli.exited;
@ -108,7 +106,7 @@ async def main():
asyncio.run(main())`); asyncio.run(main())`);
}); });
it('should print load/save storage_state', async ({ browserName, browserChannel, runCLI, testInfo }) => { test('should print load/save storage_state', async ({ browserName, browserChannel, runCLI }, testInfo) => {
const loadFileName = testInfo.outputPath('load.json'); const loadFileName = testInfo.outputPath('load.json');
const saveFileName = testInfo.outputPath('save.json'); const saveFileName = testInfo.outputPath('save.json');
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8'); await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');

View file

@ -16,16 +16,14 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { folio } from './cli.fixtures'; import { test, expect } from '../config/cliTest';
const { it, expect } = folio; const emptyHTML = new URL('file://' + path.join(__dirname, '..', '..', 'test', 'assets', 'empty.html')).toString();
const emptyHTML = new URL('file://' + path.join(__dirname, '..', 'assets', 'empty.html')).toString();
const launchOptions = (channel: string) => { const launchOptions = (channel: string) => {
return channel ? `headless=False, channel="${channel}"` : 'headless=False'; return channel ? `headless=False, channel="${channel}"` : 'headless=False';
}; };
it('should print the correct imports and context options', async ({ runCLI, browserChannel, browserName }) => { test('should print the correct imports and context options', async ({ runCLI, browserChannel, browserName }) => {
const cli = runCLI(['--target=python', emptyHTML]); const cli = runCLI(['--target=python', emptyHTML]);
const expectedResult = `from playwright.sync_api import sync_playwright const expectedResult = `from playwright.sync_api import sync_playwright
@ -36,7 +34,7 @@ def run(playwright):
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should print the correct context options for custom settings', async ({ runCLI, browserChannel, browserName }) => { test('should print the correct context options for custom settings', async ({ runCLI, browserChannel, browserName }) => {
const cli = runCLI(['--color-scheme=light', '--target=python', emptyHTML]); const cli = runCLI(['--color-scheme=light', '--target=python', emptyHTML]);
const expectedResult = `from playwright.sync_api import sync_playwright const expectedResult = `from playwright.sync_api import sync_playwright
@ -47,9 +45,9 @@ def run(playwright):
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should print the correct context options when using a device', (test, { browserName }) => { test('should print the correct context options when using a device', async ({ browserName, browserChannel, runCLI }) => {
test.skip(browserName !== 'chromium'); test.skip(browserName !== 'chromium');
}, async ({ browserChannel, runCLI }) => {
const cli = runCLI(['--device=Pixel 2', '--target=python', emptyHTML]); const cli = runCLI(['--device=Pixel 2', '--target=python', emptyHTML]);
const expectedResult = `from playwright.sync_api import sync_playwright const expectedResult = `from playwright.sync_api import sync_playwright
@ -60,9 +58,9 @@ def run(playwright):
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should print the correct context options when using a device and additional options', (test, { browserName }) => { test('should print the correct context options when using a device and additional options', async ({ browserName, browserChannel, runCLI }) => {
test.skip(browserName !== 'webkit'); test.skip(browserName !== 'webkit');
}, async ({ browserChannel, runCLI }) => {
const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', '--target=python', emptyHTML]); const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', '--target=python', emptyHTML]);
const expectedResult = `from playwright.sync_api import sync_playwright const expectedResult = `from playwright.sync_api import sync_playwright
@ -73,7 +71,7 @@ def run(playwright):
expect(cli.text()).toContain(expectedResult); expect(cli.text()).toContain(expectedResult);
}); });
it('should save the codegen output to a file if specified', async ({ runCLI, browserChannel, browserName, testInfo }) => { test('should save the codegen output to a file if specified', async ({ runCLI, browserChannel, browserName }, testInfo) => {
const tmpFile = testInfo.outputPath('script.js'); const tmpFile = testInfo.outputPath('script.js');
const cli = runCLI(['--target=python', '--output', tmpFile, emptyHTML]); const cli = runCLI(['--target=python', '--output', tmpFile, emptyHTML]);
await cli.exited; await cli.exited;
@ -101,7 +99,7 @@ with sync_playwright() as playwright:
run(playwright)`); run(playwright)`);
}); });
it('should print load/save storage_state', async ({ runCLI, browserChannel, browserName, testInfo }) => { test('should print load/save storage_state', async ({ runCLI, browserChannel, browserName }, testInfo) => {
const loadFileName = testInfo.outputPath('load.json'); const loadFileName = testInfo.outputPath('load.json');
const saveFileName = testInfo.outputPath('save.json'); const saveFileName = testInfo.outputPath('save.json');
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8'); await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');

View file

@ -100,7 +100,7 @@ class DefaultMode {
export class PlaywrightEnv implements Env<PlaywrightTestArgs> { export class PlaywrightEnv implements Env<PlaywrightTestArgs> {
private _mode: DriverMode | ServiceMode | DefaultMode; private _mode: DriverMode | ServiceMode | DefaultMode;
private _browserName: BrowserName; protected _browserName: BrowserName;
protected _options: LaunchOptions & TestOptions; protected _options: LaunchOptions & TestOptions;
protected _browserOptions: LaunchOptions; protected _browserOptions: LaunchOptions;
private _playwright: typeof import('../../index'); private _playwright: typeof import('../../index');
@ -200,6 +200,7 @@ export class PlaywrightEnv implements Env<PlaywrightTestArgs> {
} }
async afterAll(workerInfo: WorkerInfo) { async afterAll(workerInfo: WorkerInfo) {
await this._mode.teardown();
const { coverage, uninstall } = this._coverage!; const { coverage, uninstall } = this._coverage!;
uninstall(); uninstall();
const coveragePath = path.join(__dirname, '..', '..', 'test', 'coverage-report', workerInfo.workerIndex + '.json'); const coveragePath = path.join(__dirname, '..', '..', 'test', 'coverage-report', workerInfo.workerIndex + '.json');

74
tests/config/cliEnv.ts Normal file
View file

@ -0,0 +1,74 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Env, TestInfo, WorkerInfo } from '../folio/out';
import { PageEnv } from './browserEnv';
import { CLIMock, CLITestArgs, Recorder } from './cliTest';
import * as http from 'http';
import { recorderPageGetter } from '../../test/utils';
export class CLIEnv extends PageEnv implements Env<CLITestArgs> {
private _server: http.Server | undefined;
private _handler = (req: http.IncomingMessage, res: http.ServerResponse) => res.end();
private _port: number;
private _cli: CLIMock | undefined;
async beforeAll(workerInfo: WorkerInfo) {
await super.beforeAll(workerInfo);
this._port = 9907 + workerInfo.workerIndex;
this._server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => this._handler(req, res)).listen(this._port);
}
private _runCLI(args: string[]) {
this._cli = new CLIMock(this._browserName, this._browserOptions.channel, !!this._browserOptions.headless, args);
return this._cli;
}
async beforeEach(testInfo: TestInfo) {
const result = await super.beforeEach(testInfo);
const { page, context, toImpl } = result;
return {
...result,
httpServer: {
setHandler: newHandler => this._handler = newHandler,
PREFIX: `http://127.0.0.1:${this._port}`,
},
runCLI: this._runCLI.bind(this),
openRecorder: async () => {
await (page.context() as any)._enableRecorder({ language: 'javascript', startRecording: true });
const recorderPage = await recorderPageGetter(context, toImpl);
return new Recorder(page, recorderPage);
},
};
}
async afterEach(testInfo: TestInfo) {
if (this._cli) {
await this._cli.exited;
this._cli = undefined;
}
await super.afterEach(testInfo);
}
async afterAll(workerInfo: WorkerInfo) {
if (this._server) {
this._server.close();
this._server = undefined;
}
await super.afterAll(workerInfo);
}
}

View file

@ -14,54 +14,31 @@
* limitations under the License. * limitations under the License.
*/ */
import { newTestType } from '../folio/out';
import type { Page } from '../../index';
import type { ServerTestArgs } from './serverTest';
import type { BrowserTestArgs } from './browserTest';
import * as http from 'http'; import * as http from 'http';
import path from 'path'; import * as path from 'path';
import { ChildProcess, spawn } from 'child_process';
import { folio as baseFolio } from '../fixtures';
import type { BrowserType, Browser, Page } from '../..';
export { config } from 'folio';
import type { Source } from '../../src/server/supplements/recorder/recorderTypes'; import type { Source } from '../../src/server/supplements/recorder/recorderTypes';
import { recorderPageGetter } from '../utils'; import { ChildProcess, spawn } from 'child_process';
export { expect } from 'folio';
type WorkerFixtures = { interface CLIHTTPServer {
browserType: BrowserType<Browser>; setHandler: (handler: http.RequestListener) => void
browser: Browser; PREFIX: string
httpServer: httpServer; }
};
type TestFixtures = { export type CLITestArgs = BrowserTestArgs & {
recorder: Recorder; page: Page;
httpServer: CLIHTTPServer;
openRecorder: () => Promise<Recorder>;
runCLI: (args: string[]) => CLIMock; runCLI: (args: string[]) => CLIMock;
}; };
export const fixtures = baseFolio.extend<TestFixtures, WorkerFixtures>(); export const test = newTestType<CLITestArgs & ServerTestArgs>();
fixtures.recorder.init(async ({ page, context, toImpl }, runTest) => { export class Recorder {
await (page.context() as any)._enableRecorder({ language: 'javascript', startRecording: true });
const recorderPage = await recorderPageGetter(context, toImpl);
await runTest(new Recorder(page, recorderPage));
});
fixtures.httpServer.init(async ({testWorkerIndex}, runTest) => {
let handler = (req: http.IncomingMessage, res: http.ServerResponse) => res.end();
const port = 9907 + testWorkerIndex;
const server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => handler(req, res)).listen(port);
await runTest({
setHandler: newHandler => handler = newHandler,
PREFIX: `http://127.0.0.1:${port}`,
});
server.close();
}, { scope: 'worker' });
function removeAnsiColors(input: string): string {
const pattern = [
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'
].join('|');
return input.replace(new RegExp(pattern, 'g'), '');
}
class Recorder {
page: Page; page: Page;
_highlightCallback: Function _highlightCallback: Function
_highlightInstalled: boolean _highlightInstalled: boolean
@ -150,17 +127,7 @@ class Recorder {
} }
} }
fixtures.runCLI.init(async ({ browserName, browserChannel, headful }, runTest) => { export class CLIMock {
let cli: CLIMock;
const cliFactory = (args: string[]) => {
cli = new CLIMock(browserName, browserChannel, !headful, args);
return cli;
};
await runTest(cliFactory);
await cli.exited;
});
class CLIMock {
private process: ChildProcess; private process: ChildProcess;
private data: string; private data: string;
private waitForText: string; private waitForText: string;
@ -210,9 +177,10 @@ class CLIMock {
} }
} }
interface httpServer { function removeAnsiColors(input: string): string {
setHandler: (handler: http.RequestListener) => void const pattern = [
PREFIX: string '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'
].join('|');
return input.replace(new RegExp(pattern, 'g'), '');
} }
export const folio = fixtures.build();

View file

@ -20,9 +20,11 @@ import { test as playwrightTest, slowTest as playwrightSlowTest } from './playwr
import { test as browserTest } from './browserTest'; import { test as browserTest } from './browserTest';
import { test as pageTest } from './pageTest'; import { test as pageTest } from './pageTest';
import { test as electronTest } from './electronTest'; import { test as electronTest } from './electronTest';
import { test as cliTest } from './cliTest';
import { PlaywrightEnv, BrowserEnv, PageEnv, BrowserName } from './browserEnv'; import { PlaywrightEnv, BrowserEnv, PageEnv, BrowserName } from './browserEnv';
import { ServerEnv } from './serverEnv'; import { ServerEnv } from './serverEnv';
import { ElectronEnv } from './electronEnv'; import { ElectronEnv } from './electronEnv';
import { CLIEnv } from './cliEnv';
const config: Config = { const config: Config = {
testDir: path.join(__dirname, '..'), testDir: path.join(__dirname, '..'),
@ -63,6 +65,7 @@ for (const browserName of browsers) {
playwrightSlowTest.runWith(browserName, serverEnv, new PlaywrightEnv(browserName, options), { timeout: config.timeout * 3 }); playwrightSlowTest.runWith(browserName, serverEnv, new PlaywrightEnv(browserName, options), { timeout: config.timeout * 3 });
browserTest.runWith(browserName, serverEnv, new BrowserEnv(browserName, options), {}); browserTest.runWith(browserName, serverEnv, new BrowserEnv(browserName, options), {});
pageTest.runWith(browserName, serverEnv, new PageEnv(browserName, options), {}); pageTest.runWith(browserName, serverEnv, new PageEnv(browserName, options), {});
cliTest.runWith(browserName, serverEnv, new CLIEnv(browserName, options), {});
if (browserName === 'chromium') if (browserName === 'chromium')
electronTest.runWith(browserName, serverEnv, new ElectronEnv({ mode })); electronTest.runWith(browserName, serverEnv, new ElectronEnv({ mode }));
} }

View file

@ -136,15 +136,14 @@ export function newTestTypeImpl(): any {
function hook(name: string, fn: Function) { function hook(name: string, fn: Function) {
if (!currentFile) if (!currentFile)
throw errorWithCallLocation(`Hook can only be defined in a test file.`); throw errorWithCallLocation(`Hook can only be defined in a test file.`);
ensureSuiteForCurrentLocation();
suites[0]._addHook(name, fn); suites[0]._addHook(name, fn);
} }
const modifier = (type: 'skip' | 'fail' | 'fixme', arg?: boolean | string, description?: string) => { const modifier = (type: 'skip' | 'fail' | 'fixme', arg?: boolean | string, description?: string) => {
const processed = interpretCondition(arg, description);
if (!processed.condition)
return;
if (currentFile) { if (currentFile) {
const processed = interpretCondition(arg, description);
if (processed.condition)
suites[0]._annotations.push({ type, description: processed.description }); suites[0]._annotations.push({ type, description: processed.description });
return; return;
} }
@ -152,15 +151,7 @@ export function newTestTypeImpl(): any {
const testInfo = currentTestInfo(); const testInfo = currentTestInfo();
if (!testInfo) if (!testInfo)
throw new Error(`test.${type} can only be called inside the test`); throw new Error(`test.${type} can only be called inside the test`);
(testInfo[type] as any)(arg, description);
testInfo.annotations.push({ type, description: processed.description });
if (type === 'skip' || type === 'fixme') {
testInfo.expectedStatus = 'skipped';
throw new SkipError(processed.description);
} else if (type === 'fail') {
if (testInfo.expectedStatus !== 'skipped')
testInfo.expectedStatus = 'failed';
}
}; };
const test: any = spec.bind(null, 'default'); const test: any = spec.bind(null, 'default');
@ -197,9 +188,6 @@ export function newTestTypeImpl(): any {
return test; return test;
} }
export class SkipError extends Error {
}
export function setConfig(config: Config) { export function setConfig(config: Config) {
// TODO: add config validation. // TODO: add config validation.
configFile.config = config; configFile.config = config;

View file

@ -40,6 +40,23 @@ export interface Config extends RunWithConfig {
} }
export type FullConfig = Required<Config>; export type FullConfig = Required<Config>;
interface TestModifier {
skip(): void;
skip(condition: boolean): void;
skip(description: string): void;
skip(condition: boolean, description: string): void;
fixme(): void;
fixme(condition: boolean): void;
fixme(description: string): void;
fixme(condition: boolean, description: string): void;
fail(): void;
fail(condition: boolean): void;
fail(description: string): void;
fail(condition: boolean, description: string): void;
}
export type TestStatus = 'passed' | 'failed' | 'timedOut' | 'skipped'; export type TestStatus = 'passed' | 'failed' | 'timedOut' | 'skipped';
export interface WorkerInfo { export interface WorkerInfo {
@ -48,7 +65,7 @@ export interface WorkerInfo {
globalSetupResult: any; globalSetupResult: any;
} }
export interface TestInfo extends WorkerInfo { export interface TestInfo extends WorkerInfo, TestModifier {
// Declaration // Declaration
title: string; title: string;
file: string; file: string;
@ -87,7 +104,7 @@ interface TestFunction<TestArgs, TestOptions> {
(name: string, options: TestOptions, fn: (args: TestArgs, testInfo: TestInfo) => any): void; (name: string, options: TestOptions, fn: (args: TestArgs, testInfo: TestInfo) => any): void;
} }
export interface TestType<TestArgs, TestOptions> extends TestFunction<TestArgs, TestOptions> { export interface TestType<TestArgs, TestOptions> extends TestFunction<TestArgs, TestOptions>, TestModifier {
only: TestFunction<TestArgs, TestOptions>; only: TestFunction<TestArgs, TestOptions>;
describe: SuiteFunction & { describe: SuiteFunction & {
only: SuiteFunction; only: SuiteFunction;
@ -100,21 +117,6 @@ export interface TestType<TestArgs, TestOptions> extends TestFunction<TestArgs,
expect: Expect; expect: Expect;
skip(): void;
skip(condition: boolean): void;
skip(description: string): void;
skip(condition: boolean, description: string): void;
fixme(): void;
fixme(condition: boolean): void;
fixme(description: string): void;
fixme(condition: boolean, description: string): void;
fail(): void;
fail(condition: boolean): void;
fail(description: string): void;
fail(condition: boolean, description: string): void;
runWith(config?: RunWithConfig): void; runWith(config?: RunWithConfig): void;
runWith(alias: string, config?: RunWithConfig): void; runWith(alias: string, config?: RunWithConfig): void;
runWith(env: Env<TestArgs>, config?: RunWithConfig): void; runWith(env: Env<TestArgs>, config?: RunWithConfig): void;

View file

@ -17,13 +17,13 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { monotonicTime, raceAgainstDeadline, serializeError } from './util'; import { interpretCondition, monotonicTime, raceAgainstDeadline, serializeError } from './util';
import { TestBeginPayload, TestEndPayload, RunPayload, TestEntry, DonePayload, WorkerInitParams } from './ipc'; import { TestBeginPayload, TestEndPayload, RunPayload, TestEntry, DonePayload, WorkerInitParams } from './ipc';
import { setCurrentTestInfo } from './globals'; import { setCurrentTestInfo } from './globals';
import { Loader } from './loader'; import { Loader } from './loader';
import { Spec, Suite, Test } from './test'; import { Spec, Suite, Test } from './test';
import { TestInfo, WorkerInfo } from './types'; import { TestInfo, WorkerInfo } from './types';
import { SkipError, RunListDescription } from './spec'; import { RunListDescription } from './spec';
export class WorkerRunner extends EventEmitter { export class WorkerRunner extends EventEmitter {
private _params: WorkerInitParams; private _params: WorkerInitParams;
@ -230,6 +230,9 @@ export class WorkerRunner extends EventEmitter {
return path.join(basePath, ...pathSegments); return path.join(basePath, ...pathSegments);
}, },
testOptions: spec.testOptions, testOptions: spec.testOptions,
skip: (arg?: boolean | string, description?: string) => modifier(testInfo, 'skip', arg, description),
fixme: (arg?: boolean | string, description?: string) => modifier(testInfo, 'fixme', arg, description),
fail: (arg?: boolean | string, description?: string) => modifier(testInfo, 'fail', arg, description),
}; };
this._setCurrentTestInfo(testInfo); this._setCurrentTestInfo(testInfo);
@ -438,3 +441,20 @@ function buildTestEndPayload(testId: string, testInfo: TestInfo): TestEndPayload
timeout: testInfo.timeout, timeout: testInfo.timeout,
}; };
} }
function modifier(testInfo: TestInfo, type: 'skip' | 'fail' | 'fixme', arg?: boolean | string, description?: string) {
const processed = interpretCondition(arg, description);
if (!processed.condition)
return;
testInfo.annotations.push({ type, description: processed.description });
if (type === 'skip' || type === 'fixme') {
testInfo.expectedStatus = 'skipped';
throw new SkipError(processed.description);
} else if (type === 'fail') {
if (testInfo.expectedStatus !== 'skipped')
testInfo.expectedStatus = 'failed';
}
}
class SkipError extends Error {
}

View file

@ -29,9 +29,12 @@ test('should close the browser when the node process closes', async ({startRemot
expect(await remoteServer.childExitCode()).toBe(isWindows ? 1 : 0); expect(await remoteServer.childExitCode()).toBe(isWindows ? 1 : 0);
}); });
test('should report browser close signal', async ({startRemoteServer, server, platform, headful}) => { test.describe('signals', () => {
test.beforeEach(async ({platform, headful}) => {
test.skip(platform === 'win32' || headful); test.skip(platform === 'win32' || headful);
});
test('should report browser close signal', async ({startRemoteServer, server}) => {
const remoteServer = await startRemoteServer({ url: server.EMPTY_PAGE }); const remoteServer = await startRemoteServer({ url: server.EMPTY_PAGE });
const pid = await remoteServer.out('pid'); const pid = await remoteServer.out('pid');
process.kill(-pid, 'SIGTERM'); process.kill(-pid, 'SIGTERM');
@ -41,9 +44,7 @@ test('should report browser close signal', async ({startRemoteServer, server, pl
await remoteServer.childExitCode(); await remoteServer.childExitCode();
}); });
test('should report browser close signal 2', async ({startRemoteServer, server, platform, headful}) => { test('should report browser close signal 2', async ({startRemoteServer, server}) => {
test.skip(platform === 'win32' || headful);
const remoteServer = await startRemoteServer({ url: server.EMPTY_PAGE }); const remoteServer = await startRemoteServer({ url: server.EMPTY_PAGE });
const pid = await remoteServer.out('pid'); const pid = await remoteServer.out('pid');
process.kill(-pid, 'SIGKILL'); process.kill(-pid, 'SIGKILL');
@ -53,8 +54,7 @@ test('should report browser close signal 2', async ({startRemoteServer, server,
await remoteServer.childExitCode(); await remoteServer.childExitCode();
}); });
test('should close the browser on SIGINT', async ({startRemoteServer, server, browserChannel, platform, headful}) => { test('should close the browser on SIGINT', async ({startRemoteServer, server, browserChannel}) => {
test.skip(platform === 'win32' || headful);
test.fixme(!!browserChannel, 'Uncomment on roll'); test.fixme(!!browserChannel, 'Uncomment on roll');
const remoteServer = await startRemoteServer({ url: server.EMPTY_PAGE }); const remoteServer = await startRemoteServer({ url: server.EMPTY_PAGE });
@ -64,8 +64,7 @@ test('should close the browser on SIGINT', async ({startRemoteServer, server, br
expect(await remoteServer.childExitCode()).toBe(130); expect(await remoteServer.childExitCode()).toBe(130);
}); });
test('should close the browser on SIGTERM', async ({startRemoteServer, server, browserChannel, platform, headful}) => { test('should close the browser on SIGTERM', async ({startRemoteServer, server, browserChannel}) => {
test.skip(platform === 'win32' || headful);
test.fixme(!!browserChannel, 'Uncomment on roll'); test.fixme(!!browserChannel, 'Uncomment on roll');
const remoteServer = await startRemoteServer({ url: server.EMPTY_PAGE }); const remoteServer = await startRemoteServer({ url: server.EMPTY_PAGE });
@ -75,8 +74,7 @@ test('should close the browser on SIGTERM', async ({startRemoteServer, server, b
expect(await remoteServer.childExitCode()).toBe(0); expect(await remoteServer.childExitCode()).toBe(0);
}); });
test('should close the browser on SIGHUP', async ({startRemoteServer, server, browserChannel, platform, headful}) => { test('should close the browser on SIGHUP', async ({startRemoteServer, server, browserChannel}) => {
test.skip(platform === 'win32' || headful);
test.fixme(!!browserChannel, 'Uncomment on roll'); test.fixme(!!browserChannel, 'Uncomment on roll');
const remoteServer = await startRemoteServer({ url: server.EMPTY_PAGE }); const remoteServer = await startRemoteServer({ url: server.EMPTY_PAGE });
@ -86,9 +84,7 @@ test('should close the browser on SIGHUP', async ({startRemoteServer, server, br
expect(await remoteServer.childExitCode()).toBe(0); expect(await remoteServer.childExitCode()).toBe(0);
}); });
test('should kill the browser on double SIGINT', async ({startRemoteServer, server, platform, headful}) => { test('should kill the browser on double SIGINT', async ({startRemoteServer, server}) => {
test.skip(platform === 'win32' || headful);
const remoteServer = await startRemoteServer({ stallOnClose: true, url: server.EMPTY_PAGE }); const remoteServer = await startRemoteServer({ stallOnClose: true, url: server.EMPTY_PAGE });
process.kill(remoteServer.child().pid, 'SIGINT'); process.kill(remoteServer.child().pid, 'SIGINT');
await remoteServer.out('stalled'); await remoteServer.out('stalled');
@ -98,9 +94,7 @@ test('should kill the browser on double SIGINT', async ({startRemoteServer, serv
expect(await remoteServer.childExitCode()).toBe(130); expect(await remoteServer.childExitCode()).toBe(130);
}); });
test('should kill the browser on SIGINT + SIGTERM', async ({startRemoteServer, server, platform, headful}) => { test('should kill the browser on SIGINT + SIGTERM', async ({startRemoteServer, server}) => {
test.skip(platform === 'win32' || headful);
const remoteServer = await startRemoteServer({ stallOnClose: true, url: server.EMPTY_PAGE }); const remoteServer = await startRemoteServer({ stallOnClose: true, url: server.EMPTY_PAGE });
process.kill(remoteServer.child().pid, 'SIGINT'); process.kill(remoteServer.child().pid, 'SIGINT');
await remoteServer.out('stalled'); await remoteServer.out('stalled');
@ -110,9 +104,7 @@ test('should kill the browser on SIGINT + SIGTERM', async ({startRemoteServer, s
expect(await remoteServer.childExitCode()).toBe(0); expect(await remoteServer.childExitCode()).toBe(0);
}); });
test('should kill the browser on SIGTERM + SIGINT', async ({startRemoteServer, server, platform, headful}) => { test('should kill the browser on SIGTERM + SIGINT', async ({startRemoteServer, server}) => {
test.skip(platform === 'win32' || headful);
const remoteServer = await startRemoteServer({ stallOnClose: true, url: server.EMPTY_PAGE }); const remoteServer = await startRemoteServer({ stallOnClose: true, url: server.EMPTY_PAGE });
process.kill(remoteServer.child().pid, 'SIGTERM'); process.kill(remoteServer.child().pid, 'SIGTERM');
await remoteServer.out('stalled'); await remoteServer.out('stalled');
@ -121,3 +113,4 @@ test('should kill the browser on SIGTERM + SIGINT', async ({startRemoteServer, s
expect(await remoteServer.out('signal')).toBe('SIGKILL'); expect(await remoteServer.out('signal')).toBe('SIGKILL');
expect(await remoteServer.childExitCode()).toBe(130); expect(await remoteServer.childExitCode()).toBe(130);
}); });
});