참고소스 수정본
This commit is contained in:
1117
참고/playwright-main/tests/library/inspector/cli-codegen-1.spec.ts
Normal file
1117
참고/playwright-main/tests/library/inspector/cli-codegen-1.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
560
참고/playwright-main/tests/library/inspector/cli-codegen-2.spec.ts
Normal file
560
참고/playwright-main/tests/library/inspector/cli-codegen-2.spec.ts
Normal file
@@ -0,0 +1,560 @@
|
||||
/**
|
||||
* 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 { test, expect } from './inspectorTest';
|
||||
|
||||
test.describe('cli codegen', () => {
|
||||
test('should contain open page', async ({ openRecorder }) => {
|
||||
const { recorder } = await openRecorder();
|
||||
|
||||
await recorder.setContentAndWait(``);
|
||||
const sources = await recorder.waitForOutput('JavaScript', `page.goto`);
|
||||
|
||||
expect(sources.get('JavaScript')!.text).toContain(`
|
||||
const page = await context.newPage();`);
|
||||
|
||||
expect(sources.get('Java')!.text).toContain(`
|
||||
Page page = context.newPage();`);
|
||||
|
||||
expect(sources.get('Python')!.text).toContain(`
|
||||
page = context.new_page()`);
|
||||
|
||||
expect(sources.get('Python Async')!.text).toContain(`
|
||||
page = await context.new_page()`);
|
||||
|
||||
expect(sources.get('C#')!.text).toContain(`
|
||||
var page = await context.NewPageAsync();`);
|
||||
});
|
||||
|
||||
test('should contain second page', async ({ openRecorder }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
|
||||
await recorder.setContentAndWait(``);
|
||||
await page.context().newPage();
|
||||
const sources = await recorder.waitForOutput('JavaScript', 'page1');
|
||||
|
||||
expect(sources.get('JavaScript')!.text).toContain(`
|
||||
const page1 = await context.newPage();`);
|
||||
|
||||
expect(sources.get('Java')!.text).toContain(`
|
||||
Page page1 = context.newPage();`);
|
||||
|
||||
expect(sources.get('Python')!.text).toContain(`
|
||||
page1 = context.new_page()`);
|
||||
|
||||
expect(sources.get('Python Async')!.text).toContain(`
|
||||
page1 = await context.new_page()`);
|
||||
|
||||
expect(sources.get('C#')!.text).toContain(`
|
||||
var page1 = await context.NewPageAsync();`);
|
||||
});
|
||||
|
||||
test('should contain close page', async ({ openRecorder }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
|
||||
await recorder.setContentAndWait(``);
|
||||
await page.context().newPage();
|
||||
await recorder.page.close();
|
||||
const sources = await recorder.waitForOutput('JavaScript', 'page.close();');
|
||||
|
||||
expect(sources.get('JavaScript')!.text).toContain(`
|
||||
await page.close();`);
|
||||
|
||||
expect(sources.get('Java')!.text).toContain(`
|
||||
page.close();`);
|
||||
|
||||
expect(sources.get('Python')!.text).toContain(`
|
||||
page.close()`);
|
||||
|
||||
expect(sources.get('Python Async')!.text).toContain(`
|
||||
await page.close()`);
|
||||
|
||||
expect(sources.get('C#')!.text).toContain(`
|
||||
await page.CloseAsync();`);
|
||||
});
|
||||
|
||||
test('should not lead to an error if html gets clicked', async ({ openRecorder, platform, macVersion }) => {
|
||||
test.skip(platform === 'darwin' && macVersion < 15, 'recorder.page.evaluate hangs on CDP layer for some reason on macOS 14.');
|
||||
|
||||
const { page, recorder } = await openRecorder();
|
||||
|
||||
await recorder.setContentAndWait('');
|
||||
await page.context().newPage();
|
||||
const errors: any[] = [];
|
||||
recorder.page.on('pageerror', e => errors.push(e));
|
||||
await recorder.page.evaluate(() => document.querySelector('body')!.remove());
|
||||
await page.dispatchEvent('html', 'mousemove', { detail: 1 });
|
||||
await recorder.page.close();
|
||||
await recorder.waitForOutput('JavaScript', 'page.close();');
|
||||
expect(errors.length).toBe(0);
|
||||
});
|
||||
|
||||
test('should upload a single file', async ({ openRecorder, browserName, asset, isLinux }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(`
|
||||
<form>
|
||||
<input type="file">
|
||||
</form>
|
||||
`);
|
||||
|
||||
await page.focus('input[type=file]');
|
||||
await page.setInputFiles('input[type=file]', asset('file-to-upload.txt'));
|
||||
await page.click('input[type=file]');
|
||||
|
||||
const sources = await recorder.waitForOutput('JavaScript', 'setInputFiles');
|
||||
|
||||
expect(sources.get('JavaScript')!.text).toContain(`
|
||||
await page.getByRole('button', { name: 'Choose File' }).setInputFiles('file-to-upload.txt');`);
|
||||
|
||||
expect(sources.get('Java')!.text).toContain(`
|
||||
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Choose File")).setInputFiles(Paths.get("file-to-upload.txt"));`);
|
||||
|
||||
expect(sources.get('Python')!.text).toContain(`
|
||||
page.get_by_role("button", name="Choose File").set_input_files(\"file-to-upload.txt\")`);
|
||||
|
||||
expect(sources.get('Python Async')!.text).toContain(`
|
||||
await page.get_by_role("button", name="Choose File").set_input_files(\"file-to-upload.txt\")`);
|
||||
|
||||
expect(sources.get('C#')!.text).toContain(`
|
||||
await page.GetByRole(AriaRole.Button, new() { Name = "Choose File" }).SetInputFilesAsync(new[] { \"file-to-upload.txt\" });`);
|
||||
});
|
||||
|
||||
test('should upload multiple files', async ({ openRecorder, browserName, asset, isLinux }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(`
|
||||
<form>
|
||||
<input type="file" multiple>
|
||||
</form>
|
||||
`);
|
||||
|
||||
await page.focus('input[type=file]');
|
||||
await page.setInputFiles('input[type=file]', [asset('file-to-upload.txt'), asset('file-to-upload-2.txt')]);
|
||||
await page.click('input[type=file]');
|
||||
|
||||
const sources = await recorder.waitForOutput('JavaScript', 'setInputFiles');
|
||||
|
||||
expect(sources.get('JavaScript')!.text).toContain(`
|
||||
await page.getByRole('button', { name: 'Choose File' }).setInputFiles(['file-to-upload.txt', 'file-to-upload-2.txt']);`);
|
||||
|
||||
expect(sources.get('Java')!.text).toContain(`
|
||||
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Choose File")).setInputFiles(new Path[] {Paths.get("file-to-upload.txt"), Paths.get("file-to-upload-2.txt")});`);
|
||||
|
||||
expect(sources.get('Python')!.text).toContain(`
|
||||
page.get_by_role("button", name="Choose File").set_input_files([\"file-to-upload.txt\", \"file-to-upload-2.txt\"]`);
|
||||
|
||||
expect(sources.get('Python Async')!.text).toContain(`
|
||||
await page.get_by_role("button", name="Choose File").set_input_files([\"file-to-upload.txt\", \"file-to-upload-2.txt\"]`);
|
||||
|
||||
expect(sources.get('C#')!.text).toContain(`
|
||||
await page.GetByRole(AriaRole.Button, new() { Name = "Choose File" }).SetInputFilesAsync(new[] { \"file-to-upload.txt\", \"file-to-upload-2.txt\" });`);
|
||||
});
|
||||
|
||||
test('should clear files', async ({ openRecorder, browserName, asset, isLinux }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(`
|
||||
<form>
|
||||
<input type="file" multiple>
|
||||
</form>
|
||||
`);
|
||||
await page.focus('input[type=file]');
|
||||
await page.setInputFiles('input[type=file]', asset('file-to-upload.txt'));
|
||||
await page.setInputFiles('input[type=file]', []);
|
||||
await page.click('input[type=file]');
|
||||
|
||||
const sources = await recorder.waitForOutput('JavaScript', 'setInputFiles');
|
||||
|
||||
expect(sources.get('JavaScript')!.text).toContain(`
|
||||
await page.getByRole('button', { name: 'Choose File' }).setInputFiles([]);`);
|
||||
|
||||
expect(sources.get('Java')!.text).toContain(`
|
||||
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Choose File")).setInputFiles(new Path[0]);`);
|
||||
|
||||
expect(sources.get('Python')!.text).toContain(`
|
||||
page.get_by_role("button", name="Choose File").set_input_files([])`);
|
||||
|
||||
expect(sources.get('Python Async')!.text).toContain(`
|
||||
await page.get_by_role("button", name="Choose File").set_input_files([])`);
|
||||
|
||||
expect(sources.get('C#')!.text).toContain(`
|
||||
await page.GetByRole(AriaRole.Button, new() { Name = "Choose File" }).SetInputFilesAsync(new[] { });`);
|
||||
});
|
||||
|
||||
test('should download files', async ({ openRecorder, server }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
|
||||
server.setRoute('/download', (req, res) => {
|
||||
const pathName = new URL(req.url, 'http://localhost').pathname;
|
||||
if (pathName === '/download') {
|
||||
res.setHeader('Content-Type', 'application/octet-stream');
|
||||
res.setHeader('Content-Disposition', 'attachment; filename=file.txt');
|
||||
res.end(`Hello world`);
|
||||
} else {
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
res.end('');
|
||||
}
|
||||
});
|
||||
await recorder.setContentAndWait(`
|
||||
<a href="${server.PREFIX}/download" download>Download</a>
|
||||
`, server.PREFIX);
|
||||
await recorder.hoverOverElement('a');
|
||||
await Promise.all([
|
||||
page.waitForEvent('download'),
|
||||
page.click('a')
|
||||
]);
|
||||
const sources = await recorder.waitForOutput('JavaScript', 'await downloadPromise');
|
||||
|
||||
expect.soft(sources.get('JavaScript')!.text).toContain(`
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
await page.getByRole('link', { name: 'Download' }).click();
|
||||
const download = await downloadPromise;`);
|
||||
|
||||
expect.soft(sources.get('Java')!.text).toContain(`
|
||||
Download download = page.waitForDownload(() -> {
|
||||
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("Download")).click();
|
||||
});`);
|
||||
|
||||
expect.soft(sources.get('Python')!.text).toContain(`
|
||||
with page.expect_download() as download_info:
|
||||
page.get_by_role("link", name="Download").click()
|
||||
download = download_info.value`);
|
||||
|
||||
expect.soft(sources.get('Python Async')!.text).toContain(`
|
||||
async with page.expect_download() as download_info:
|
||||
await page.get_by_role("link", name="Download").click()
|
||||
download = await download_info.value`);
|
||||
|
||||
expect.soft(sources.get('C#')!.text).toContain(`
|
||||
var download = await page.RunAndWaitForDownloadAsync(async () =>
|
||||
{
|
||||
await page.GetByRole(AriaRole.Link, new() { Name = "Download" }).ClickAsync();
|
||||
});`);
|
||||
});
|
||||
|
||||
test('should handle dialogs', async ({ openRecorder }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
|
||||
await recorder.setContentAndWait(`
|
||||
<button onclick="alert()">click me</button>
|
||||
`);
|
||||
await recorder.hoverOverElement('button');
|
||||
page.once('dialog', async dialog => {
|
||||
await dialog.dismiss();
|
||||
});
|
||||
await page.click('button');
|
||||
|
||||
const sources = await recorder.waitForOutput('JavaScript', 'once');
|
||||
|
||||
expect.soft(sources.get('JavaScript')!.text).toContain(`
|
||||
page.once('dialog', dialog => {
|
||||
console.log(\`Dialog message: \${dialog.message()}\`);
|
||||
dialog.dismiss().catch(() => {});
|
||||
});
|
||||
await page.getByRole('button', { name: 'click me' }).click();`);
|
||||
|
||||
expect.soft(sources.get('Java')!.text).toContain(`
|
||||
page.onceDialog(dialog -> {
|
||||
System.out.println(String.format("Dialog message: %s", dialog.message()));
|
||||
dialog.dismiss();
|
||||
});
|
||||
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("click me")).click();`);
|
||||
|
||||
expect.soft(sources.get('Python')!.text).toContain(`
|
||||
page.once(\"dialog\", lambda dialog: dialog.dismiss())
|
||||
page.get_by_role("button", name="click me").click()`);
|
||||
|
||||
expect.soft(sources.get('Python Async')!.text).toContain(`
|
||||
page.once(\"dialog\", lambda dialog: dialog.dismiss())
|
||||
await page.get_by_role("button", name="click me").click()`);
|
||||
|
||||
expect.soft(sources.get('C#')!.text).toContain(`
|
||||
void page_Dialog_EventHandler(object sender, IDialog dialog)
|
||||
{
|
||||
Console.WriteLine($\"Dialog message: {dialog.Message}\");
|
||||
dialog.DismissAsync();
|
||||
page.Dialog -= page_Dialog_EventHandler;
|
||||
}
|
||||
page.Dialog += page_Dialog_EventHandler;
|
||||
await page.GetByRole(AriaRole.Button, new() { Name = "click me" }).ClickAsync();`);
|
||||
|
||||
});
|
||||
|
||||
test('should handle history.postData', async ({ openRecorder, server }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
|
||||
await recorder.setContentAndWait(`
|
||||
<script>
|
||||
let seqNum = 0;
|
||||
function pushState() {
|
||||
history.pushState({}, 'title', '${server.PREFIX}/#seqNum=' + (++seqNum));
|
||||
}
|
||||
</script>`, server.PREFIX);
|
||||
for (let i = 1; i < 3; ++i) {
|
||||
await page.evaluate('pushState()');
|
||||
await recorder.waitForOutput('JavaScript', `await page.goto('${server.PREFIX}/#seqNum=${i}');`);
|
||||
}
|
||||
});
|
||||
|
||||
test('should record open in a new tab with url', async ({ openRecorder, browserName }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(`<a href="about:blank?foo">link</a>`);
|
||||
|
||||
const locator = await recorder.hoverOverElement('a');
|
||||
expect(locator).toBe(`getByRole('link', { name: 'link' })`);
|
||||
|
||||
await page.click('a', { modifiers: ['ControlOrMeta'] });
|
||||
const sources = await recorder.waitForOutput('JavaScript', 'page1');
|
||||
|
||||
if (browserName !== 'firefox') {
|
||||
expect(sources.get('JavaScript')!.text).toContain(`
|
||||
const page1 = await context.newPage();
|
||||
await page1.goto('about:blank?foo');`);
|
||||
expect(sources.get('Python Async')!.text).toContain(`
|
||||
page1 = await context.new_page()
|
||||
await page1.goto("about:blank?foo")`);
|
||||
expect(sources.get('C#')!.text).toContain(`
|
||||
var page1 = await context.NewPageAsync();
|
||||
await page1.GotoAsync("about:blank?foo");`);
|
||||
} else {
|
||||
expect(sources.get('JavaScript')!.text).toContain(`
|
||||
const page1Promise = page.waitForEvent('popup');
|
||||
await page.getByRole('link', { name: 'link' }).click({
|
||||
modifiers: ['ControlOrMeta']
|
||||
});
|
||||
const page1 = await page1Promise;`);
|
||||
}
|
||||
});
|
||||
|
||||
test('should not clash pages', async ({ openRecorder, browserName }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
const [popup1] = await Promise.all([
|
||||
page.context().waitForEvent('page'),
|
||||
page.evaluate(`window.open('about:blank')`)
|
||||
]);
|
||||
await recorder.setPageContentAndWait(popup1, '<input id=name>');
|
||||
|
||||
const [popup2] = await Promise.all([
|
||||
page.context().waitForEvent('page'),
|
||||
page.evaluate(`window.open('about:blank')`)
|
||||
]);
|
||||
await recorder.setPageContentAndWait(popup2, '<input id=name>');
|
||||
|
||||
await popup1.type('input', 'TextA');
|
||||
await recorder.waitForOutput('JavaScript', 'TextA');
|
||||
|
||||
await popup2.type('input', 'TextB');
|
||||
await recorder.waitForOutput('JavaScript', 'TextB');
|
||||
|
||||
const sources = recorder.sources();
|
||||
expect(sources.get('JavaScript')!.text).toContain(`await page1.locator('#name').fill('TextA');`);
|
||||
expect(sources.get('JavaScript')!.text).toContain(`await page2.locator('#name').fill('TextB');`);
|
||||
|
||||
expect(sources.get('Java')!.text).toContain(`page1.locator("#name").fill("TextA");`);
|
||||
expect(sources.get('Java')!.text).toContain(`page2.locator("#name").fill("TextB");`);
|
||||
|
||||
expect(sources.get('Python')!.text).toContain(`page1.locator("#name").fill("TextA")`);
|
||||
expect(sources.get('Python')!.text).toContain(`page2.locator("#name").fill("TextB")`);
|
||||
|
||||
expect(sources.get('Python Async')!.text).toContain(`await page1.locator("#name").fill("TextA")`);
|
||||
expect(sources.get('Python Async')!.text).toContain(`await page2.locator("#name").fill("TextB")`);
|
||||
|
||||
expect(sources.get('C#')!.text).toContain(`await page1.Locator("#name").FillAsync("TextA");`);
|
||||
expect(sources.get('C#')!.text).toContain(`await page2.Locator("#name").FillAsync("TextB");`);
|
||||
});
|
||||
|
||||
test('click should emit events in order', async ({ openRecorder }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
|
||||
await recorder.setContentAndWait(`
|
||||
<button id=button>
|
||||
<script>
|
||||
button.addEventListener('mousedown', e => console.log(e.type));
|
||||
button.addEventListener('mouseup', e => console.log(e.type));
|
||||
button.addEventListener('click', e => console.log(e.type));
|
||||
</script>
|
||||
`);
|
||||
|
||||
const messages: any[] = [];
|
||||
page.on('console', message => {
|
||||
if (message.type() !== 'error')
|
||||
messages.push(message.text());
|
||||
});
|
||||
await Promise.all([
|
||||
page.click('button'),
|
||||
recorder.waitForOutput('JavaScript', '.click(')
|
||||
]);
|
||||
await expect.poll(() => messages).toEqual(['mousedown', 'mouseup', 'click']);
|
||||
});
|
||||
|
||||
test('should reset hover model on action when element detaches', async ({ openRecorder }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
|
||||
await recorder.setContentAndWait(`<input id="checkbox" onclick="document.getElementById('checkbox').remove()">`);
|
||||
const [models] = await Promise.all([
|
||||
recorder.waitForActionPerformed(),
|
||||
page.click('input')
|
||||
]);
|
||||
expect(models.hovered).toBe(null);
|
||||
});
|
||||
|
||||
test('should update active model on action', async ({ openRecorder, browserName, headless }) => {
|
||||
test.skip(browserName === 'webkit', 'webkit does not actually focus the input after click');
|
||||
|
||||
const { page, recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="checkbox.name='updated'"></input>`);
|
||||
const [models] = await Promise.all([
|
||||
recorder.waitForActionPerformed(),
|
||||
page.click('input')
|
||||
]);
|
||||
expect(models.active).toBe('#checkbox');
|
||||
});
|
||||
|
||||
test('should check input with chaining id', async ({ openRecorder }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(`<input id="checkbox" type="checkbox" name="accept" onchange="checkbox.name = 'updated'"></input>`);
|
||||
await Promise.all([
|
||||
recorder.waitForActionPerformed(),
|
||||
page.click('input[id=checkbox]')
|
||||
]);
|
||||
});
|
||||
|
||||
test('should record navigations after identical pushState', async ({ openRecorder, server }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
server.setRoute('/page2.html', (req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
res.end('Hello world');
|
||||
});
|
||||
await recorder.setContentAndWait(`
|
||||
<script>
|
||||
function pushState() {
|
||||
history.pushState({}, 'title', '${server.PREFIX}');
|
||||
}
|
||||
</script>`, server.PREFIX);
|
||||
for (let i = 1; i < 3; ++i)
|
||||
await page.evaluate('pushState()');
|
||||
|
||||
await page.goto(server.PREFIX + '/page2.html');
|
||||
await recorder.waitForOutput('JavaScript', `await page.goto('${server.PREFIX}/page2.html');`);
|
||||
});
|
||||
|
||||
test('should fill tricky characters', async ({ openRecorder }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
|
||||
await recorder.setContentAndWait(`<textarea spellcheck=false id="textarea" name="name" oninput="console.log(textarea.value)"></textarea>`);
|
||||
const locator = await recorder.focusElement('textarea');
|
||||
expect(locator).toBe(`locator('#textarea')`);
|
||||
|
||||
const [message, sources] = await Promise.all([
|
||||
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
||||
recorder.waitForOutput('JavaScript', 'fill'),
|
||||
page.fill('textarea', 'Hello\'\"\`\nWorld')
|
||||
]);
|
||||
|
||||
expect(sources.get('JavaScript')!.text).toContain(`
|
||||
await page.locator('#textarea').fill('Hello\\'"\`\\nWorld');`);
|
||||
|
||||
expect(sources.get('Java')!.text).toContain(`
|
||||
page.locator("#textarea").fill("Hello'\\"\`\\nWorld");`);
|
||||
|
||||
expect(sources.get('Python')!.text).toContain(`
|
||||
page.locator("#textarea").fill(\"Hello'\\"\`\\nWorld\")`);
|
||||
|
||||
expect(sources.get('Python Async')!.text).toContain(`
|
||||
await page.locator("#textarea").fill(\"Hello'\\"\`\\nWorld\")`);
|
||||
|
||||
expect(sources.get('C#')!.text).toContain(`
|
||||
await page.Locator("#textarea").FillAsync(\"Hello'\\"\`\\nWorld\");`);
|
||||
|
||||
expect(message.text()).toBe('Hello\'\"\`\nWorld');
|
||||
});
|
||||
|
||||
test('should --test-id-attribute', async ({ openRecorder }) => {
|
||||
const { page, recorder } = await openRecorder({ testIdAttributeName: 'my-test-id' });
|
||||
|
||||
await recorder.setContentAndWait(`<div my-test-id="foo">Hello</div>`);
|
||||
await page.click('[my-test-id=foo]');
|
||||
const sources = await recorder.waitForOutput('JavaScript', `page.getByTestId`);
|
||||
|
||||
expect.soft(sources.get('JavaScript')!.text).toContain(`await page.getByTestId('foo').click()`);
|
||||
expect.soft(sources.get('Java')!.text).toContain(`page.getByTestId("foo").click()`);
|
||||
expect.soft(sources.get('Python')!.text).toContain(`page.get_by_test_id("foo").click()`);
|
||||
expect.soft(sources.get('Python Async')!.text).toContain(`await page.get_by_test_id("foo").click()`);
|
||||
expect.soft(sources.get('C#')!.text).toContain(`await page.GetByTestId("foo").ClickAsync();`);
|
||||
});
|
||||
|
||||
test('should auto-generate toBeVisible', async ({ openRecorder }) => {
|
||||
const { page, recorder } = await openRecorder();
|
||||
|
||||
await recorder.setContentAndWait(`
|
||||
<button id=one>one</button>
|
||||
<div id=insertion></div>
|
||||
<button id=two>two</button>
|
||||
<script>
|
||||
const one = document.getElementById('one');
|
||||
const insertion = document.getElementById('insertion');
|
||||
one.addEventListener('click', () => {
|
||||
insertion.innerHTML = '<h2>new header</h2>';
|
||||
console.log('clicked one');
|
||||
});
|
||||
two.addEventListener('click', () => {
|
||||
console.log('clicked two');
|
||||
});
|
||||
</script>
|
||||
`);
|
||||
await recorder.recorderPage.getByRole('button', { name: 'Settings' }).click();
|
||||
await recorder.recorderPage.getByRole('checkbox', { name: 'Generate assertions' }).check();
|
||||
|
||||
const locatorOne = await recorder.hoverOverElement('#one');
|
||||
expect(locatorOne).toBe(`getByRole('button', { name: 'one' })`);
|
||||
await Promise.all([
|
||||
page.waitForEvent('console', msg => msg.text() === 'clicked one'),
|
||||
recorder.waitForOutput('JavaScript', 'one'),
|
||||
recorder.trustedClick(),
|
||||
]);
|
||||
|
||||
const locatorTwo = await recorder.hoverOverElement('#two');
|
||||
expect(locatorTwo).toBe(`getByRole('button', { name: 'two' })`);
|
||||
const [sources] = await Promise.all([
|
||||
recorder.waitForOutput('JavaScript', 'two'),
|
||||
page.waitForEvent('console', msg => msg.text() === 'clicked two'),
|
||||
recorder.trustedClick(),
|
||||
]);
|
||||
|
||||
expect.soft(sources.get('Playwright Test')!.text).toContain(`
|
||||
await expect(page.getByRole('heading', { name: 'new header' })).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'two' }).click();`);
|
||||
|
||||
expect.soft(sources.get('Python')!.text).toContain(`
|
||||
expect(page.get_by_role("heading", name="new header")).to_be_visible()
|
||||
|
||||
page.get_by_role("button", name="two").click()`);
|
||||
|
||||
expect.soft(sources.get('Python Async')!.text).toContain(`
|
||||
await expect(page.get_by_role("heading", name="new header")).to_be_visible()
|
||||
|
||||
await page.get_by_role("button", name="two").click()`);
|
||||
|
||||
expect.soft(sources.get('Java')!.text).toContain(`
|
||||
assertThat(page.getByRole(AriaRole.HEADING, new Page.GetByRoleOptions().setName("new header"))).isVisible();
|
||||
|
||||
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("two")).click();`);
|
||||
|
||||
expect.soft(sources.get('C#')!.text).toContain(`
|
||||
await Expect(page.GetByRole(AriaRole.Heading, new() { Name = "new header" })).ToBeVisibleAsync();
|
||||
|
||||
await page.GetByRole(AriaRole.Button, new() { Name = "two" }).ClickAsync();`);
|
||||
});
|
||||
});
|
||||
1087
참고/playwright-main/tests/library/inspector/cli-codegen-3.spec.ts
Normal file
1087
참고/playwright-main/tests/library/inspector/cli-codegen-3.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* 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 { test, expect } from './inspectorTest';
|
||||
import { roundBox } from '../../config/utils';
|
||||
|
||||
test.describe(() => {
|
||||
test('should generate aria snapshot', async ({ openRecorder }) => {
|
||||
const { recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(`<main><button>Submit</button></main>`);
|
||||
|
||||
await recorder.page.click('x-pw-tool-item.snapshot');
|
||||
await recorder.page.hover('button');
|
||||
await recorder.trustedClick();
|
||||
|
||||
await expect.poll(() =>
|
||||
recorder.text('JavaScript')).toContain(`await expect(page.getByRole('button')).toMatchAriaSnapshot(\`- button "Submit"\`);`);
|
||||
await expect.poll(() =>
|
||||
recorder.text('Python')).toContain(`expect(page.get_by_role("button")).to_match_aria_snapshot("- button \\"Submit\\"")`);
|
||||
await expect.poll(() =>
|
||||
recorder.text('Python Async')).toContain(`await expect(page.get_by_role(\"button\")).to_match_aria_snapshot("- button \\"Submit\\"")`);
|
||||
await expect.poll(() =>
|
||||
recorder.text('Java')).toContain(`assertThat(page.getByRole(AriaRole.BUTTON)).matchesAriaSnapshot("- button \\"Submit\\"");`);
|
||||
await expect.poll(() =>
|
||||
recorder.text('C#')).toContain(`await Expect(page.GetByRole(AriaRole.Button)).ToMatchAriaSnapshotAsync("- button \\"Submit\\"");`);
|
||||
});
|
||||
|
||||
test('should generate regex in aria snapshot', async ({ openRecorder }) => {
|
||||
const { recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(`<main><button>Submit 123</button></main>`);
|
||||
|
||||
await recorder.page.click('x-pw-tool-item.snapshot');
|
||||
await recorder.page.hover('button');
|
||||
await recorder.trustedClick();
|
||||
|
||||
await expect.poll(() =>
|
||||
recorder.text('JavaScript')).toContain(`await expect(page.getByRole('button')).toMatchAriaSnapshot(\`- button /Submit \\\\d+/\`);`);
|
||||
await expect.poll(() =>
|
||||
recorder.text('Python')).toContain(`expect(page.get_by_role("button")).to_match_aria_snapshot("- button /Submit \\\\d+/")`);
|
||||
await expect.poll(() =>
|
||||
recorder.text('Python Async')).toContain(`await expect(page.get_by_role(\"button\")).to_match_aria_snapshot("- button /Submit \\\\d+/")`);
|
||||
await expect.poll(() =>
|
||||
recorder.text('Java')).toContain(`assertThat(page.getByRole(AriaRole.BUTTON)).matchesAriaSnapshot("- button /Submit \\\\d+/");`);
|
||||
await expect.poll(() =>
|
||||
recorder.text('C#')).toContain(`await Expect(page.GetByRole(AriaRole.Button)).ToMatchAriaSnapshotAsync("- button /Submit \\\\d+/");`);
|
||||
});
|
||||
|
||||
test('should generate regex for uuid in aria snapshot', async ({ openRecorder }) => {
|
||||
const { recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(`<main><a href="/items/550e8400-e29b-41d4-a716-446655440000">Item 550e8400-e29b-41d4-a716-446655440000</a></main>`);
|
||||
|
||||
await recorder.page.click('x-pw-tool-item.snapshot');
|
||||
await recorder.page.hover('a');
|
||||
await recorder.trustedClick();
|
||||
|
||||
// url still contains full UUID, we can improve here.
|
||||
await expect.poll(() =>
|
||||
recorder.text('JavaScript')).toContain(`- link /Item [0-9a-fA-F-]+/:`);
|
||||
});
|
||||
|
||||
test('should inspect aria snapshot', async ({ openRecorder }) => {
|
||||
const { recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(`<main><button>Submit</button></main>`);
|
||||
await recorder.page.click('x-pw-tool-item.pick-locator');
|
||||
await recorder.page.hover('button');
|
||||
await recorder.trustedClick();
|
||||
await recorder.recorderPage.getByRole('tab', { name: 'Aria' }).click();
|
||||
await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(`
|
||||
- textbox
|
||||
- text: '- button "Submit"'
|
||||
`);
|
||||
});
|
||||
|
||||
test('should update aria snapshot highlight', async ({ openRecorder }) => {
|
||||
const { recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(`<main>
|
||||
<button>Submit</button>
|
||||
<button>Cancel</button>
|
||||
</main>`);
|
||||
|
||||
const submitButton = recorder.page.getByRole('button', { name: 'Submit' });
|
||||
const cancelButton = recorder.page.getByRole('button', { name: 'Cancel' });
|
||||
|
||||
|
||||
await recorder.page.click('x-pw-tool-item.pick-locator');
|
||||
await submitButton.hover();
|
||||
await recorder.trustedClick();
|
||||
await recorder.recorderPage.getByRole('tab', { name: 'Aria' }).click();
|
||||
await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(`
|
||||
- text: '- button "Submit"'
|
||||
`);
|
||||
|
||||
await recorder.recorderPage.locator('.tab-aria .CodeMirror').click();
|
||||
for (let i = 0; i < '"Submit"'.length; i++)
|
||||
await recorder.recorderPage.keyboard.press('Backspace');
|
||||
|
||||
{
|
||||
// No accessible name => two boxes.
|
||||
const box11 = roundBox(await submitButton.boundingBox());
|
||||
const box12 = roundBox(await recorder.page.locator('x-pw-highlight').first().boundingBox());
|
||||
expect(box11).toEqual(box12);
|
||||
|
||||
const box21 = roundBox(await cancelButton.boundingBox());
|
||||
const box22 = roundBox(await recorder.page.locator('x-pw-highlight').last().boundingBox());
|
||||
expect(box21).toEqual(box22);
|
||||
}
|
||||
|
||||
{
|
||||
// Different button.
|
||||
await recorder.recorderPage.locator('.tab-aria .CodeMirror').pressSequentially('"Cancel"');
|
||||
await expect(recorder.page.locator('x-pw-highlight')).toBeVisible();
|
||||
const box1 = roundBox(await cancelButton.boundingBox());
|
||||
const box2 = roundBox(await recorder.page.locator('x-pw-highlight').boundingBox());
|
||||
expect(box1).toEqual(box2);
|
||||
}
|
||||
});
|
||||
|
||||
test('should show aria snapshot error', async ({ openRecorder }) => {
|
||||
const { recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(`<main>
|
||||
<button>Submit</button>
|
||||
<button>Cancel</button>
|
||||
</main>`);
|
||||
|
||||
const submitButton = recorder.page.getByRole('button', { name: 'Submit' });
|
||||
|
||||
await recorder.page.click('x-pw-tool-item.pick-locator');
|
||||
await submitButton.hover();
|
||||
await recorder.trustedClick();
|
||||
|
||||
await recorder.recorderPage.getByRole('tab', { name: 'Aria' }).click();
|
||||
await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(`
|
||||
- text: '- button "Submit"'
|
||||
`);
|
||||
|
||||
await recorder.recorderPage.locator('.tab-aria .CodeMirror').click();
|
||||
await recorder.recorderPage.keyboard.press('Backspace');
|
||||
// 1 highlighted token
|
||||
await expect(recorder.recorderPage.locator('.source-line-error-underline')).toHaveCount(1);
|
||||
});
|
||||
|
||||
test('should generate valid javascript with multiline snapshot assertion', async ({ openRecorder }) => {
|
||||
const { recorder } = await openRecorder();
|
||||
// set width and height to 100% to ensure click is outside of the list
|
||||
await recorder.setContentAndWait(`<body style="width:100%;height:100%"><ul><li>item 1</li><li>item 2</li></ul></body>`);
|
||||
|
||||
await recorder.page.click('x-pw-tool-item.snapshot');
|
||||
await recorder.page.hover('body');
|
||||
await recorder.trustedClick();
|
||||
|
||||
// playwright tests assertions are uncommented
|
||||
await expect.poll(() =>
|
||||
recorder.text('Playwright Test')).toContain([
|
||||
` await expect(page.locator('body')).toMatchAriaSnapshot(\``,
|
||||
` - list:`,
|
||||
` - listitem: item 1`,
|
||||
` - listitem: item 2`,
|
||||
` \`);`,
|
||||
].join('\n'));
|
||||
|
||||
// non-test javascript has commented assertions
|
||||
await expect.poll(() =>
|
||||
recorder.text('JavaScript')).toContain([
|
||||
` // await expect(page.locator('body')).toMatchAriaSnapshot(\``,
|
||||
` // - list:`,
|
||||
` // - listitem: item 1`,
|
||||
` // - listitem: item 2`,
|
||||
` // \`);`,
|
||||
].join('\n'));
|
||||
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* 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 fs from 'fs';
|
||||
import { test, expect } from './inspectorTest';
|
||||
|
||||
const launchOptions = (channel: string) => {
|
||||
return channel ? `Channel = "${channel}",\n Headless = false,` : `Headless = false,`;
|
||||
};
|
||||
|
||||
function capitalize(browserName: string): string {
|
||||
return browserName[0].toUpperCase() + browserName.slice(1);
|
||||
}
|
||||
|
||||
test('should print the correct imports and context options', async ({ browserName, channel, runCLI, server }) => {
|
||||
const cli = runCLI(['--target=csharp', server.EMPTY_PAGE]);
|
||||
const expectedResult = `using Microsoft.Playwright;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using var playwright = await Playwright.CreateAsync();
|
||||
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(new()
|
||||
{
|
||||
${launchOptions(channel)}
|
||||
});
|
||||
var context = await browser.NewContextAsync();`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options for custom settings', async ({ browserName, channel, runCLI, server, proxyServer }) => {
|
||||
proxyServer.forwardTo(server.PORT);
|
||||
const cli = runCLI([
|
||||
'--color-scheme=dark',
|
||||
'--geolocation=37.819722,-122.478611',
|
||||
'--lang=es',
|
||||
'--proxy-server=' + proxyServer.HOST,
|
||||
'--timezone=Europe/Rome',
|
||||
'--user-agent=hardkodemium',
|
||||
'--viewport-size=1280,720',
|
||||
'--target=csharp',
|
||||
server.EMPTY_PAGE]);
|
||||
const expectedResult = `
|
||||
using var playwright = await Playwright.CreateAsync();
|
||||
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(new()
|
||||
{
|
||||
${launchOptions(channel)}
|
||||
Proxy = new()
|
||||
{
|
||||
Server = "${proxyServer.HOST}",
|
||||
},
|
||||
});
|
||||
var context = await browser.NewContextAsync(new()
|
||||
{
|
||||
ColorScheme = ColorScheme.Dark,
|
||||
Geolocation = new()
|
||||
{
|
||||
Latitude = 37.819722m,
|
||||
Longitude = -122.478611m,
|
||||
},
|
||||
Locale = "es",
|
||||
Permissions = new[] { "geolocation" },
|
||||
TimezoneId = "Europe/Rome",
|
||||
UserAgent = "hardkodemium",
|
||||
ViewportSize = new()
|
||||
{
|
||||
Height = 720,
|
||||
Width = 1280,
|
||||
},
|
||||
});`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options when using a device', async ({ browserName, channel, runCLI, server }) => {
|
||||
test.skip(browserName !== 'chromium');
|
||||
|
||||
const cli = runCLI(['--device=Pixel 2', '--target=csharp', server.EMPTY_PAGE]);
|
||||
const expectedResult = `
|
||||
using var playwright = await Playwright.CreateAsync();
|
||||
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(new()
|
||||
{
|
||||
${launchOptions(channel)}
|
||||
});
|
||||
var context = await browser.NewContextAsync(playwright.Devices["Pixel 2"]);`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options when using a device and additional options', async ({ browserName, channel, runCLI, server, proxyServer }) => {
|
||||
test.skip(browserName !== 'webkit');
|
||||
|
||||
proxyServer.forwardTo(server.PORT);
|
||||
const cli = runCLI([
|
||||
'--device=iPhone 11',
|
||||
'--color-scheme=dark',
|
||||
'--geolocation=37.819722,-122.478611',
|
||||
'--lang=es',
|
||||
'--proxy-server=' + proxyServer.HOST,
|
||||
'--timezone=Europe/Rome',
|
||||
'--user-agent=hardkodemium',
|
||||
'--viewport-size=1280,720',
|
||||
'--target=csharp',
|
||||
server.EMPTY_PAGE]);
|
||||
const expectedResult = `
|
||||
using var playwright = await Playwright.CreateAsync();
|
||||
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(new()
|
||||
{
|
||||
${launchOptions(channel)}
|
||||
Proxy = new()
|
||||
{
|
||||
Server = "${proxyServer.HOST}",
|
||||
},
|
||||
});
|
||||
var context = await browser.NewContextAsync(new()
|
||||
{
|
||||
ColorScheme = ColorScheme.Dark,
|
||||
Geolocation = new()
|
||||
{
|
||||
Latitude = 37.819722m,
|
||||
Longitude = -122.478611m,
|
||||
},${process.platform === 'linux' && browserName === 'webkit' ? '' : `
|
||||
HasTouch = true,
|
||||
IsMobile = true,`}
|
||||
Locale = "es",
|
||||
Permissions = new[] { "geolocation" },
|
||||
Screen = new()
|
||||
{
|
||||
Height = 896,
|
||||
Width = 414,
|
||||
},
|
||||
TimezoneId = "Europe/Rome",
|
||||
UserAgent = "hardkodemium",
|
||||
ViewportSize = new()
|
||||
{
|
||||
Height = 720,
|
||||
Width = 1280,
|
||||
},
|
||||
});`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print load/save storageState', async ({ browserName, channel, runCLI, server }, testInfo) => {
|
||||
const loadFileName = testInfo.outputPath('load.json');
|
||||
const saveFileName = testInfo.outputPath('save.json');
|
||||
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
|
||||
const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, '--target=csharp', server.EMPTY_PAGE]);
|
||||
const expectedResult1 = `
|
||||
using var playwright = await Playwright.CreateAsync();
|
||||
await using var browser = await playwright.${capitalize(browserName)}.LaunchAsync(new()
|
||||
{
|
||||
${launchOptions(channel)}
|
||||
});
|
||||
var context = await browser.NewContextAsync(new()
|
||||
{
|
||||
StorageStatePath = "${loadFileName.replace(/\\/g, '\\\\')}",
|
||||
});`;
|
||||
await cli.waitFor(expectedResult1);
|
||||
const expectedResult2 = `
|
||||
await context.StorageStateAsync(new()
|
||||
{
|
||||
Path = "${saveFileName.replace(/\\/g, '\\\\')}"
|
||||
});
|
||||
`;
|
||||
await cli.waitFor(expectedResult2);
|
||||
});
|
||||
|
||||
test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `await context.RouteFromHARAsync(${JSON.stringify(harFileName)});`;
|
||||
const cli = runCLI(['--target=csharp', `--save-har=${harFileName}`]);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
|
||||
test('should work with --save-har and --save-har-glob', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `await context.RouteFromHARAsync(${JSON.stringify(harFileName)}, new()
|
||||
{
|
||||
Url = "**/*.js",
|
||||
});`;
|
||||
const cli = runCLI(['--target=csharp', `--save-har=${harFileName}`, '--save-har-glob=**/*.js']);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
|
||||
for (const testFramework of ['nunit', 'mstest', 'xunit'] as const) {
|
||||
test(`should not print context options method override in ${testFramework} if no options were passed`, async ({ runCLI, server }) => {
|
||||
const cli = runCLI([`--target=csharp-${testFramework}`, server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`Page.GotoAsync("${server.EMPTY_PAGE}")`);
|
||||
expect(await cli.text()).not.toContain('public override BrowserNewContextOptions ContextOptions()');
|
||||
});
|
||||
|
||||
test(`should print context options method override in ${testFramework} if options were passed`, async ({ runCLI, server }) => {
|
||||
const cli = runCLI([`--target=csharp-${testFramework}`, '--color-scheme=dark', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`Page.GotoAsync("${server.EMPTY_PAGE}")`);
|
||||
expect(await cli.text()).toContain(` public override BrowserNewContextOptions ContextOptions()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ColorScheme = ColorScheme.Dark,
|
||||
};
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test(`should work with --save-har in ${testFramework}`, async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `await Context.RouteFromHARAsync(${JSON.stringify(harFileName)});`;
|
||||
const cli = runCLI([`--target=csharp-${testFramework}`, `--save-har=${harFileName}`]);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
|
||||
test(`should work with --save-har and --save-har-glob in ${testFramework}`, async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `await Context.RouteFromHARAsync(${JSON.stringify(harFileName)}, new()
|
||||
{
|
||||
Url = "**/*.js",
|
||||
});`;
|
||||
const cli = runCLI([`--target=csharp-${testFramework}`, `--save-har=${harFileName}`, '--save-har-glob=**/*.js']);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
}
|
||||
|
||||
test(`should print a valid basic program in mstest`, async ({ runCLI, server }) => {
|
||||
const cli = runCLI([`--target=csharp-mstest`, '--color-scheme=dark', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`Page.GotoAsync("${server.EMPTY_PAGE}")`);
|
||||
const expected = `using Microsoft.Playwright.MSTest;
|
||||
using Microsoft.Playwright;
|
||||
|
||||
[TestClass]
|
||||
public class Tests : PageTest
|
||||
{
|
||||
public override BrowserNewContextOptions ContextOptions()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ColorScheme = ColorScheme.Dark,
|
||||
};
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task MyTest()
|
||||
{
|
||||
await Page.GotoAsync("${server.EMPTY_PAGE}");
|
||||
}
|
||||
}`;
|
||||
expect(await cli.text()).toContain(expected);
|
||||
});
|
||||
|
||||
test(`should print a valid basic program in nunit`, async ({ runCLI, server }) => {
|
||||
const cli = runCLI([`--target=csharp-nunit`, '--color-scheme=dark', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`Page.GotoAsync("${server.EMPTY_PAGE}")`);
|
||||
const expected = `using Microsoft.Playwright.NUnit;
|
||||
using Microsoft.Playwright;
|
||||
|
||||
[Parallelizable(ParallelScope.Self)]
|
||||
[TestFixture]
|
||||
public class Tests : PageTest
|
||||
{
|
||||
public override BrowserNewContextOptions ContextOptions()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ColorScheme = ColorScheme.Dark,
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task MyTest()
|
||||
{
|
||||
await Page.GotoAsync("${server.EMPTY_PAGE}");
|
||||
}
|
||||
}`;
|
||||
expect(await cli.text()).toContain(expected);
|
||||
});
|
||||
|
||||
test(`should print a valid basic program in xunit`, async ({ runCLI, server }) => {
|
||||
const cli = runCLI([`--target=csharp-xunit`, '--color-scheme=dark', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`Page.GotoAsync("${server.EMPTY_PAGE}")`);
|
||||
const expected = `using Microsoft.Playwright.Xunit;
|
||||
using Microsoft.Playwright;
|
||||
using Xunit;
|
||||
|
||||
public class Tests : PageTest
|
||||
{
|
||||
public override BrowserNewContextOptions ContextOptions()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ColorScheme = ColorScheme.Dark,
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MyTest()
|
||||
{
|
||||
await Page.GotoAsync("${server.EMPTY_PAGE}");
|
||||
}
|
||||
}`;
|
||||
expect(await cli.text()).toContain(expected);
|
||||
});
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 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 fs from 'fs';
|
||||
import { test, expect } from './inspectorTest';
|
||||
|
||||
const launchOptions = (channel: string) => {
|
||||
return channel ? `.setChannel("${channel}")\n .setHeadless(false)` : '.setHeadless(false)';
|
||||
};
|
||||
|
||||
test('should print the correct imports and context options', async ({ runCLI, channel, browserName, server }) => {
|
||||
const cli = runCLI(['--target=java', server.EMPTY_PAGE]);
|
||||
const expectedResult = `import com.microsoft.playwright.*;
|
||||
import com.microsoft.playwright.options.*;
|
||||
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||
import java.util.*;
|
||||
|
||||
public class Example {
|
||||
public static void main(String[] args) {
|
||||
try (Playwright playwright = Playwright.create()) {
|
||||
Browser browser = playwright.${browserName}().launch(new BrowserType.LaunchOptions()
|
||||
${launchOptions(channel)});
|
||||
BrowserContext context = browser.newContext();`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options for custom settings', async ({ runCLI, server }) => {
|
||||
const cli = runCLI(['--color-scheme=light', '--target=java', server.EMPTY_PAGE]);
|
||||
const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
||||
.setColorScheme(ColorScheme.LIGHT));`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options when using a device', async ({ browserName, runCLI, server }) => {
|
||||
test.skip(browserName !== 'chromium');
|
||||
|
||||
const cli = runCLI(['--device=Pixel 2', '--target=java', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`.setViewportSize(411, 731));`);
|
||||
const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
||||
.setDeviceScaleFactor(2.625)
|
||||
.setHasTouch(true)
|
||||
.setIsMobile(true)
|
||||
.setUserAgent("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/XXXX Mobile Safari/537.36")
|
||||
.setViewportSize(411, 731));`;
|
||||
const text = await cli.text();
|
||||
expect(text.replace(/(.*Chrome\/)(.*?)( .*)/m, '$1XXXX$3')).toContain(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options when using a device and additional options', async ({ browserName, runCLI, server }) => {
|
||||
test.skip(browserName !== 'webkit');
|
||||
|
||||
const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', '--target=java', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`.setViewportSize(414, 715));`);
|
||||
const expectedResult = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
||||
.setColorScheme(ColorScheme.LIGHT)
|
||||
.setDeviceScaleFactor(2)
|
||||
.setHasTouch(true)
|
||||
.setIsMobile(true)
|
||||
.setUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/XXXX Mobile/15E148 Safari/604.1")
|
||||
.setViewportSize(414, 715));`;
|
||||
const text = await cli.text();
|
||||
expect(text.replace(/(.*Version\/)(.*?)( .*)/m, '$1XXXX$3')).toContain(expectedResult);
|
||||
});
|
||||
|
||||
test('should print load/save storage_state', async ({ runCLI, server }, testInfo) => {
|
||||
const loadFileName = testInfo.outputPath('load.json');
|
||||
const saveFileName = testInfo.outputPath('save.json');
|
||||
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
|
||||
const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, '--target=java', server.EMPTY_PAGE]);
|
||||
const expectedResult1 = `BrowserContext context = browser.newContext(new Browser.NewContextOptions()
|
||||
.setStorageStatePath(Paths.get(${JSON.stringify(loadFileName)})));`;
|
||||
await cli.waitFor(expectedResult1);
|
||||
|
||||
const expectedResult2 = `
|
||||
context.storageState(new BrowserContext.StorageStateOptions().setPath("${saveFileName.replace(/\\/g, '\\\\')}"))`;
|
||||
await cli.waitFor(expectedResult2);
|
||||
});
|
||||
|
||||
test('should work with --save-har and --save-har-glob as java-library', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `context.routeFromHAR(Paths.get(${JSON.stringify(harFileName)}), new BrowserContext.RouteFromHAROptions()
|
||||
.setUrl("**/*.js"));`;
|
||||
const cli = runCLI(['--target=java', `--save-har=${harFileName}`, '--save-har-glob=**/*.js']);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
|
||||
test('should work with --save-har and --save-har-glob as java-junit', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `page.routeFromHAR(Paths.get(${JSON.stringify(harFileName)}), new Page.RouteFromHAROptions()
|
||||
.setUrl("**/*.js"));`;
|
||||
const cli = runCLI(['--target=java-junit', `--save-har=${harFileName}`, '--save-har-glob=**/*.js']);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
|
||||
test('should print the correct imports in junit', async ({ runCLI, server }) => {
|
||||
const cli = runCLI(['--target=java-junit', server.EMPTY_PAGE]);
|
||||
const expectedImportResult = `import com.microsoft.playwright.junit.UsePlaywright;
|
||||
import com.microsoft.playwright.Page;
|
||||
import com.microsoft.playwright.options.*;
|
||||
|
||||
import org.junit.jupiter.api.*;
|
||||
import static com.microsoft.playwright.assertions.PlaywrightAssertions.*;`;
|
||||
await cli.waitFor(expectedImportResult);
|
||||
});
|
||||
|
||||
test('should print a valid basic program in junit', async ({ runCLI, server }) => {
|
||||
const cli = runCLI(['--target=java-junit', server.EMPTY_PAGE]);
|
||||
const expectedResult = `import com.microsoft.playwright.junit.UsePlaywright;
|
||||
import com.microsoft.playwright.Page;
|
||||
import com.microsoft.playwright.options.*;
|
||||
|
||||
import org.junit.jupiter.api.*;
|
||||
import static com.microsoft.playwright.assertions.PlaywrightAssertions.*;
|
||||
|
||||
@UsePlaywright
|
||||
public class TestExample {
|
||||
@Test
|
||||
void test(Page page) {
|
||||
page.navigate("${server.EMPTY_PAGE}");
|
||||
}
|
||||
}`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 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 fs from 'fs';
|
||||
import { test } from './inspectorTest';
|
||||
|
||||
const launchOptions = (channel: string) => {
|
||||
return channel ? `channel: '${channel}',\n headless: false` : 'headless: false';
|
||||
};
|
||||
|
||||
test('should print the correct imports and context options', async ({ browserName, channel, runCLI, server }) => {
|
||||
const cli = runCLI(['--target=javascript', server.EMPTY_PAGE]);
|
||||
const expectedResult = `const { ${browserName} } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await ${browserName}.launch({
|
||||
${launchOptions(channel)}
|
||||
});
|
||||
const context = await browser.newContext();`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options for custom settings', async ({ browserName, channel, runCLI, server }) => {
|
||||
const cli = runCLI(['--color-scheme=light', '--target=javascript', server.EMPTY_PAGE]);
|
||||
const expectedResult = `const { ${browserName} } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await ${browserName}.launch({
|
||||
${launchOptions(channel)}
|
||||
});
|
||||
const context = await browser.newContext({
|
||||
colorScheme: 'light'
|
||||
});`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
|
||||
test('should print the correct context options when using a device', async ({ browserName, channel, runCLI, server }) => {
|
||||
test.skip(browserName !== 'chromium');
|
||||
|
||||
const cli = runCLI(['--device=Pixel 2', '--target=javascript', server.EMPTY_PAGE]);
|
||||
const expectedResult = `const { chromium, devices } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({
|
||||
${launchOptions(channel)}
|
||||
});
|
||||
const context = await browser.newContext({
|
||||
...devices['Pixel 2'],
|
||||
});`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options when using a device and additional options', async ({ browserName, channel, runCLI, server }) => {
|
||||
test.skip(browserName !== 'webkit');
|
||||
|
||||
const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', '--target=javascript', server.EMPTY_PAGE]);
|
||||
const expectedResult = `const { webkit, devices } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await webkit.launch({
|
||||
${launchOptions(channel)}
|
||||
});
|
||||
const context = await browser.newContext({
|
||||
...devices['iPhone 11'],
|
||||
colorScheme: 'light'
|
||||
});`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should save the codegen output to a file if specified', async ({ browserName, channel, runCLI, server }, testInfo) => {
|
||||
const cli = runCLI(['--target=javascript', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`const { ${browserName} } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await ${browserName}.launch({
|
||||
${launchOptions(channel)}
|
||||
});
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
await page.goto('${server.EMPTY_PAGE}');
|
||||
|
||||
// ---------------------
|
||||
await context.close();
|
||||
await browser.close();
|
||||
})();`);
|
||||
});
|
||||
|
||||
test('should print load/save storageState', async ({ browserName, channel, runCLI, server }, testInfo) => {
|
||||
const loadFileName = testInfo.outputPath('load.json');
|
||||
const saveFileName = testInfo.outputPath('save.json');
|
||||
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
|
||||
const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, '--target=javascript', server.EMPTY_PAGE]);
|
||||
const expectedResult1 = `const { ${browserName} } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await ${browserName}.launch({
|
||||
${launchOptions(channel)}
|
||||
});
|
||||
const context = await browser.newContext({
|
||||
storageState: '${loadFileName.replace(/\\/g, '\\\\')}'
|
||||
});`;
|
||||
await cli.waitFor(expectedResult1);
|
||||
|
||||
const expectedResult2 = `
|
||||
// ---------------------
|
||||
await context.storageState({ path: '${saveFileName.replace(/\\/g, '\\\\')}' });
|
||||
await context.close();
|
||||
await browser.close();
|
||||
})();`;
|
||||
await cli.waitFor(expectedResult2);
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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 { test, expect } from './inspectorTest';
|
||||
import { roundBox } from '../../config/utils';
|
||||
|
||||
test.describe(() => {
|
||||
test('should inspect locator', async ({ openRecorder }) => {
|
||||
const { recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(`<main><button>Submit</button></main>`);
|
||||
await recorder.page.click('x-pw-tool-item.pick-locator');
|
||||
await recorder.page.hover('button');
|
||||
await recorder.trustedClick();
|
||||
await recorder.recorderPage.getByRole('tab', { name: 'Locator' }).click();
|
||||
await expect(recorder.recorderPage.locator('.tab-locator .CodeMirror')).toMatchAriaSnapshot(`
|
||||
- text: "getByRole('button', { name: 'Submit' })"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should update locator highlight', async ({ openRecorder }) => {
|
||||
const { recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(`<main>
|
||||
<button>Submit</button>
|
||||
<button>Cancel</button>
|
||||
</main>`);
|
||||
|
||||
const submitButton = recorder.page.getByRole('button', { name: 'Submit' });
|
||||
const cancelButton = recorder.page.getByRole('button', { name: 'Cancel' });
|
||||
|
||||
await recorder.recorderPage.getByRole('button', { name: 'Record' }).click();
|
||||
|
||||
await recorder.page.click('x-pw-tool-item.pick-locator');
|
||||
await submitButton.hover();
|
||||
await recorder.trustedClick();
|
||||
await recorder.recorderPage.getByRole('tab', { name: 'Locator' }).click();
|
||||
await expect(recorder.recorderPage.locator('.tab-locator .CodeMirror')).toMatchAriaSnapshot(`
|
||||
- text: "getByRole('button', { name: 'Submit' })"
|
||||
`);
|
||||
|
||||
await recorder.recorderPage.locator('.tab-locator .CodeMirror').click();
|
||||
for (let i = 0; i < `Submit' })`.length; i++)
|
||||
await recorder.recorderPage.keyboard.press('Backspace');
|
||||
|
||||
{
|
||||
// Different button.
|
||||
await recorder.recorderPage.locator('.tab-locator .CodeMirror').pressSequentially(`Cancel' })`);
|
||||
await expect(recorder.page.locator('x-pw-highlight')).toBeVisible();
|
||||
const box1 = roundBox(await cancelButton.boundingBox());
|
||||
const box2 = roundBox(await recorder.page.locator('x-pw-highlight').boundingBox());
|
||||
expect(box1).toEqual(box2);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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 fs from 'fs';
|
||||
import { test, expect } from './inspectorTest';
|
||||
|
||||
test('should print the correct imports and context options', async ({ runCLI, server }) => {
|
||||
const cli = runCLI(['--target=python-pytest', server.EMPTY_PAGE]);
|
||||
const expectedResult = `import re
|
||||
from playwright.sync_api import Page, expect
|
||||
|
||||
|
||||
def test_example(page: Page) -> None:`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options when using a device and lang', async ({ browserName, runCLI, server }, testInfo) => {
|
||||
test.skip(browserName !== 'webkit');
|
||||
|
||||
const cli = runCLI(['--target=python-pytest', '--device=iPhone 11', '--lang=en-US', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`import pytest
|
||||
import re
|
||||
from playwright.sync_api import Page, expect
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def browser_context_args(browser_context_args, playwright):
|
||||
return {**playwright.devices["iPhone 11"], "locale": "en-US"}
|
||||
|
||||
|
||||
def test_example(page: Page) -> None:
|
||||
page.goto("${server.EMPTY_PAGE}")
|
||||
`);
|
||||
});
|
||||
|
||||
test('should save the codegen output to a file if specified', async ({ runCLI, server }, testInfo) => {
|
||||
const cli = runCLI(['--target=python-pytest', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`import re
|
||||
from playwright.sync_api import Page, expect
|
||||
|
||||
|
||||
def test_example(page: Page) -> None:
|
||||
page.goto("${server.EMPTY_PAGE}")
|
||||
`);
|
||||
});
|
||||
|
||||
test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `page.route_from_har(${JSON.stringify(harFileName)})`;
|
||||
const cli = runCLI(['--target=python-pytest', `--save-har=${harFileName}`]);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
|
||||
test('should work with --save-har and --save-har-glob', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `page.route_from_har(${JSON.stringify(harFileName)}, url="**/*.js")`;
|
||||
const cli = runCLI(['--target=python-pytest', `--save-har=${harFileName}`, '--save-har-glob=**/*.js']);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* 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 fs from 'fs';
|
||||
import { test, expect } from './inspectorTest';
|
||||
|
||||
const launchOptions = (channel: string) => {
|
||||
return channel ? `channel="${channel}", headless=False` : 'headless=False';
|
||||
};
|
||||
|
||||
test('should print the correct imports and context options', async ({ browserName, channel, runCLI, server }) => {
|
||||
const cli = runCLI(['--target=python-async', server.EMPTY_PAGE]);
|
||||
const expectedResult = `import asyncio
|
||||
import re
|
||||
from playwright.async_api import Playwright, async_playwright, expect
|
||||
|
||||
|
||||
async def run(playwright: Playwright) -> None:
|
||||
browser = await playwright.${browserName}.launch(${launchOptions(channel)})
|
||||
context = await browser.new_context()`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options for custom settings', async ({ browserName, channel, runCLI, server }) => {
|
||||
const cli = runCLI(['--color-scheme=light', '--target=python-async', server.EMPTY_PAGE]);
|
||||
const expectedResult = `import asyncio
|
||||
import re
|
||||
from playwright.async_api import Playwright, async_playwright, expect
|
||||
|
||||
|
||||
async def run(playwright: Playwright) -> None:
|
||||
browser = await playwright.${browserName}.launch(${launchOptions(channel)})
|
||||
context = await browser.new_context(color_scheme="light")`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options when using a device', async ({ browserName, channel, runCLI, server }) => {
|
||||
test.skip(browserName !== 'chromium');
|
||||
|
||||
const cli = runCLI(['--device=Pixel 2', '--target=python-async', server.EMPTY_PAGE]);
|
||||
const expectedResult = `import asyncio
|
||||
import re
|
||||
from playwright.async_api import Playwright, async_playwright, expect
|
||||
|
||||
|
||||
async def run(playwright: Playwright) -> None:
|
||||
browser = await playwright.chromium.launch(${launchOptions(channel)})
|
||||
context = await browser.new_context(**playwright.devices["Pixel 2"])`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options when using a device and additional options', async ({ browserName, channel, runCLI, server }) => {
|
||||
test.skip(browserName !== 'webkit');
|
||||
|
||||
const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', '--target=python-async', server.EMPTY_PAGE]);
|
||||
const expectedResult = `import asyncio
|
||||
import re
|
||||
from playwright.async_api import Playwright, async_playwright, expect
|
||||
|
||||
|
||||
async def run(playwright: Playwright) -> None:
|
||||
browser = await playwright.webkit.launch(${launchOptions(channel)})
|
||||
context = await browser.new_context(**playwright.devices["iPhone 11"], color_scheme="light")`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should save the codegen output to a file if specified', async ({ browserName, channel, runCLI, server }, testInfo) => {
|
||||
const cli = runCLI(['--target=python-async', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`import asyncio
|
||||
import re
|
||||
from playwright.async_api import Playwright, async_playwright, expect
|
||||
|
||||
|
||||
async def run(playwright: Playwright) -> None:
|
||||
browser = await playwright.${browserName}.launch(${launchOptions(channel)})
|
||||
context = await browser.new_context()
|
||||
page = await context.new_page()
|
||||
await page.goto("${server.EMPTY_PAGE}")
|
||||
|
||||
# ---------------------
|
||||
await context.close()
|
||||
await browser.close()
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with async_playwright() as playwright:
|
||||
await run(playwright)
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
`);
|
||||
});
|
||||
|
||||
test('should print load/save storage_state', async ({ browserName, channel, runCLI, server }, testInfo) => {
|
||||
const loadFileName = testInfo.outputPath('load.json');
|
||||
const saveFileName = testInfo.outputPath('save.json');
|
||||
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
|
||||
const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, '--target=python-async', server.EMPTY_PAGE]);
|
||||
const expectedResult1 = `import asyncio
|
||||
import re
|
||||
from playwright.async_api import Playwright, async_playwright, expect
|
||||
|
||||
|
||||
async def run(playwright: Playwright) -> None:
|
||||
browser = await playwright.${browserName}.launch(${launchOptions(channel)})
|
||||
context = await browser.new_context(storage_state="${loadFileName.replace(/\\/g, '\\\\')}")`;
|
||||
await cli.waitFor(expectedResult1);
|
||||
|
||||
const expectedResult2 = `
|
||||
# ---------------------
|
||||
await context.storage_state(path="${saveFileName.replace(/\\/g, '\\\\')}")
|
||||
await context.close()
|
||||
await browser.close()
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with async_playwright() as playwright:
|
||||
await run(playwright)
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
`;
|
||||
await cli.waitFor(expectedResult2);
|
||||
});
|
||||
|
||||
test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `await context.route_from_har(${JSON.stringify(harFileName)})`;
|
||||
const cli = runCLI(['--target=python-async', `--save-har=${harFileName}`]);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
|
||||
test('should work with --save-har and --save-har-glob', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `await context.route_from_har(${JSON.stringify(harFileName)}, url="**/*.js")`;
|
||||
const cli = runCLI(['--target=python-async', `--save-har=${harFileName}`, '--save-har-glob=**/*.js']);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* 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 fs from 'fs';
|
||||
import { test, expect } from './inspectorTest';
|
||||
|
||||
const launchOptions = (channel: string) => {
|
||||
return channel ? `channel="${channel}", headless=False` : 'headless=False';
|
||||
};
|
||||
|
||||
test('should print the correct imports and context options', async ({ runCLI, channel, browserName, server }) => {
|
||||
const cli = runCLI(['--target=python', server.EMPTY_PAGE]);
|
||||
const expectedResult = `import re
|
||||
from playwright.sync_api import Playwright, sync_playwright, expect
|
||||
|
||||
|
||||
def run(playwright: Playwright) -> None:
|
||||
browser = playwright.${browserName}.launch(${launchOptions(channel)})
|
||||
context = browser.new_context()`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options for custom settings', async ({ runCLI, channel, browserName, server }) => {
|
||||
const cli = runCLI(['--color-scheme=light', '--target=python', server.EMPTY_PAGE]);
|
||||
const expectedResult = `import re
|
||||
from playwright.sync_api import Playwright, sync_playwright, expect
|
||||
|
||||
|
||||
def run(playwright: Playwright) -> None:
|
||||
browser = playwright.${browserName}.launch(${launchOptions(channel)})
|
||||
context = browser.new_context(color_scheme="light")`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options when using a device', async ({ browserName, channel, runCLI, server }) => {
|
||||
test.skip(browserName !== 'chromium');
|
||||
|
||||
const cli = runCLI(['--device=Pixel 2', '--target=python', server.EMPTY_PAGE]);
|
||||
const expectedResult = `import re
|
||||
from playwright.sync_api import Playwright, sync_playwright, expect
|
||||
|
||||
|
||||
def run(playwright: Playwright) -> None:
|
||||
browser = playwright.chromium.launch(${launchOptions(channel)})
|
||||
context = browser.new_context(**playwright.devices["Pixel 2"])`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options when using a device and additional options', async ({ browserName, channel, runCLI, server }) => {
|
||||
test.skip(browserName !== 'webkit');
|
||||
|
||||
const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', '--target=python', server.EMPTY_PAGE]);
|
||||
const expectedResult = `import re
|
||||
from playwright.sync_api import Playwright, sync_playwright, expect
|
||||
|
||||
|
||||
def run(playwright: Playwright) -> None:
|
||||
browser = playwright.webkit.launch(${launchOptions(channel)})
|
||||
context = browser.new_context(**playwright.devices["iPhone 11"], color_scheme="light")`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should save the codegen output to a file if specified', async ({ runCLI, channel, browserName, server }, testInfo) => {
|
||||
const cli = runCLI(['--target=python', server.EMPTY_PAGE]);
|
||||
await cli.waitFor(`import re
|
||||
from playwright.sync_api import Playwright, sync_playwright, expect
|
||||
|
||||
|
||||
def run(playwright: Playwright) -> None:
|
||||
browser = playwright.${browserName}.launch(${launchOptions(channel)})
|
||||
context = browser.new_context()
|
||||
page = context.new_page()
|
||||
page.goto("${server.EMPTY_PAGE}")
|
||||
|
||||
# ---------------------
|
||||
context.close()
|
||||
browser.close()
|
||||
|
||||
|
||||
with sync_playwright() as playwright:
|
||||
run(playwright)
|
||||
`);
|
||||
});
|
||||
|
||||
test('should print load/save storage_state', async ({ runCLI, channel, browserName, server }, testInfo) => {
|
||||
const loadFileName = testInfo.outputPath('load.json');
|
||||
const saveFileName = testInfo.outputPath('save.json');
|
||||
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
|
||||
const cli = runCLI([`--load-storage=${loadFileName}`, `--save-storage=${saveFileName}`, '--target=python', server.EMPTY_PAGE]);
|
||||
const expectedResult1 = `import re
|
||||
from playwright.sync_api import Playwright, sync_playwright, expect
|
||||
|
||||
|
||||
def run(playwright: Playwright) -> None:
|
||||
browser = playwright.${browserName}.launch(${launchOptions(channel)})
|
||||
context = browser.new_context(storage_state="${loadFileName.replace(/\\/g, '\\\\')}")`;
|
||||
await cli.waitFor(expectedResult1);
|
||||
|
||||
const expectedResult2 = `
|
||||
# ---------------------
|
||||
context.storage_state(path="${saveFileName.replace(/\\/g, '\\\\')}")
|
||||
context.close()
|
||||
browser.close()
|
||||
|
||||
|
||||
with sync_playwright() as playwright:
|
||||
run(playwright)
|
||||
`;
|
||||
await cli.waitFor(expectedResult2);
|
||||
});
|
||||
|
||||
test('should work with --save-har', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `context.route_from_har(${JSON.stringify(harFileName)})`;
|
||||
const cli = runCLI(['--target=python-async', `--save-har=${harFileName}`]);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
|
||||
test('should work with --save-har and --save-har-glob', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `context.route_from_har(${JSON.stringify(harFileName)}, url="**/*.js")`;
|
||||
const cli = runCLI(['--target=python-async', `--save-har=${harFileName}`, '--save-har-glob=**/*.js']);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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 fs from 'fs';
|
||||
import { test, expect } from './inspectorTest';
|
||||
|
||||
test('should print the correct imports and context options', async ({ runCLI, server }) => {
|
||||
const cli = runCLI([server.EMPTY_PAGE]);
|
||||
const expectedResult = `import { test, expect } from '@playwright/test';
|
||||
|
||||
test('test', async ({ page }) => {
|
||||
await page.goto('${server.EMPTY_PAGE}');
|
||||
});`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options for custom settings', async ({ runCLI, server }) => {
|
||||
const cli = runCLI(['--color-scheme=light', server.EMPTY_PAGE]);
|
||||
const expectedResult = `import { test, expect } from '@playwright/test';
|
||||
|
||||
test.use({
|
||||
colorScheme: 'light'
|
||||
});
|
||||
|
||||
test('test', async ({ page }) => {`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
|
||||
test('should print the correct context options when using a device', async ({ browserName, runCLI, server }) => {
|
||||
test.skip(browserName !== 'chromium');
|
||||
|
||||
const cli = runCLI(['--device=Pixel 2', server.EMPTY_PAGE]);
|
||||
const expectedResult = `import { test, expect, devices } from '@playwright/test';
|
||||
|
||||
test.use({
|
||||
...devices['Pixel 2'],
|
||||
});
|
||||
|
||||
test('test', async ({ page }) => {`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print the correct context options when using a device and additional options', async ({ browserName, server, runCLI }) => {
|
||||
test.skip(browserName !== 'webkit');
|
||||
|
||||
const cli = runCLI(['--color-scheme=light', '--device=iPhone 11', server.EMPTY_PAGE]);
|
||||
const expectedResult = `import { test, expect, devices } from '@playwright/test';
|
||||
|
||||
test.use({
|
||||
...devices['iPhone 11'],
|
||||
colorScheme: 'light'
|
||||
});
|
||||
|
||||
test('test', async ({ page }) => {`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should print load storageState', async ({ runCLI, server }, testInfo) => {
|
||||
const loadFileName = testInfo.outputPath('load.json');
|
||||
await fs.promises.writeFile(loadFileName, JSON.stringify({ cookies: [], origins: [] }), 'utf8');
|
||||
const cli = runCLI([`--load-storage=${loadFileName}`, server.EMPTY_PAGE]);
|
||||
const expectedResult = `import { test, expect } from '@playwright/test';
|
||||
|
||||
test.use({
|
||||
storageState: '${loadFileName.replace(/\\/g, '\\\\')}'
|
||||
});
|
||||
|
||||
test('test', async ({ page }) => {`;
|
||||
await cli.waitFor(expectedResult);
|
||||
});
|
||||
|
||||
test('should not generate recordHAR with --save-har', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = ` await page.routeFromHAR('${harFileName.replace(/\\/g, '\\\\')}');`;
|
||||
const cli = runCLI(['--target=playwright-test', `--save-har=${harFileName}`]);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
|
||||
test('should generate routeFromHAR with --save-har', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `test('test', async ({ page }) => {
|
||||
await page.routeFromHAR('${harFileName.replace(/\\/g, '\\\\')}');
|
||||
});`;
|
||||
const cli = runCLI(['--target=playwright-test', `--save-har=${harFileName}`]);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
|
||||
test('should generate routeFromHAR with --save-har and --save-har-glob', async ({ runCLI }, testInfo) => {
|
||||
const harFileName = testInfo.outputPath('har.har');
|
||||
const expectedResult = `test('test', async ({ page }) => {
|
||||
await page.routeFromHAR('${harFileName.replace(/\\/g, '\\\\')}', {
|
||||
url: '**/*.js'
|
||||
});
|
||||
});`;
|
||||
const cli = runCLI(['--target=playwright-test', `--save-har=${harFileName}`, '--save-har-glob=**/*.js']);
|
||||
await cli.waitFor(expectedResult);
|
||||
await cli.exit();
|
||||
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
|
||||
expect(json.log.creator.name).toBe('Playwright');
|
||||
});
|
||||
121
참고/playwright-main/tests/library/inspector/console-api.spec.ts
Normal file
121
참고/playwright-main/tests/library/inspector/console-api.spec.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 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 { test as it, expect } from './inspectorTest';
|
||||
|
||||
let scriptPromise: Promise<void>;
|
||||
|
||||
it.beforeEach(async ({ page, recorderPageGetter }) => {
|
||||
scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
})();
|
||||
await recorderPageGetter();
|
||||
});
|
||||
|
||||
it.afterEach(async ({ recorderPageGetter }) => {
|
||||
const recorderPage = await recorderPageGetter();
|
||||
recorderPage.click('[title="Resume (F8)"]').catch(() => {});
|
||||
await scriptPromise;
|
||||
recorderPage.click('[title="Resume (F8)"]').catch(() => {});
|
||||
});
|
||||
|
||||
it('should support playwright.$, playwright.$$', async ({ page }) => {
|
||||
const body = await page.evaluateHandle('playwright.$("body")');
|
||||
expect(await body.evaluate<string, HTMLBodyElement>((node: HTMLBodyElement) => node.nodeName)).toBe('BODY');
|
||||
const length = await page.evaluate('playwright.$$("body").length');
|
||||
expect(length).toBe(1);
|
||||
});
|
||||
|
||||
it('should support playwright.selector', async ({ page }) => {
|
||||
const length = await page.evaluate('playwright.selector(document.body)');
|
||||
expect(length).toBe('body');
|
||||
});
|
||||
|
||||
it('should support playwright.locator.value', async ({ page }) => {
|
||||
await page.setContent('<div>Hello<div>');
|
||||
const handle = await page.evaluateHandle(`playwright.locator('div', { hasText: 'Hello' }).element`);
|
||||
expect(await handle.evaluate<string, HTMLDivElement>((node: HTMLDivElement) => node.nodeName)).toBe('DIV');
|
||||
});
|
||||
|
||||
it('should support playwright.locator.values', async ({ page }) => {
|
||||
await page.setContent('<div>Hello<div>Bar</div></div>');
|
||||
expect(await page.evaluate(`playwright.locator('div', { hasText: 'Hello' }).elements.length`)).toBe(1);
|
||||
expect(await page.evaluate(`playwright.locator('div', { hasText: 'HElLo' }).elements.length`)).toBe(1);
|
||||
expect(await page.evaluate(`playwright.locator('div', { hasText: /ELL/ }).elements.length`)).toBe(0);
|
||||
expect(await page.evaluate(`playwright.locator('div', { hasText: /ELL/i }).elements.length`)).toBe(1);
|
||||
expect(await page.evaluate(`playwright.locator('div', { hasText: /Hello/ }).elements.length`)).toBe(1);
|
||||
expect(await page.evaluate(`playwright.locator('div', { hasNotText: /Bar/ }).elements.length`)).toBe(0);
|
||||
expect(await page.evaluate(`playwright.locator('div', { hasNotText: /Hello/ }).elements.length`)).toBe(1);
|
||||
});
|
||||
|
||||
it('should support playwright.locator({ has })', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<div>Hi</div>
|
||||
<div><span>Hello</span></div>
|
||||
<div><span>dont match</span></div>
|
||||
`);
|
||||
expect(await page.evaluate(`playwright.locator('div', { has: playwright.locator('span') }).element.innerHTML`)).toContain('Hello');
|
||||
expect(await page.evaluate(`playwright.locator('div', { has: playwright.locator('text=Hello') }).element.innerHTML`)).toContain('span');
|
||||
expect(await page.evaluate(`playwright.locator('div', { has: playwright.locator('span', { hasText: 'Hello' }) }).elements.length`)).toBe(1);
|
||||
});
|
||||
|
||||
it('should support playwright.locator({ hasNot })', async ({ page }) => {
|
||||
await page.setContent('<div>Hi</div><div><span>Hello</span></div>');
|
||||
expect(await page.evaluate(`playwright.locator('div', { hasNot: playwright.locator('span') }).element.innerHTML`)).toContain('Hi');
|
||||
expect(await page.evaluate(`playwright.locator('div', { hasNot: playwright.locator('text=Hello') }).element.innerHTML`)).toContain('Hi');
|
||||
});
|
||||
|
||||
it('should support locator.and()', async ({ page }) => {
|
||||
await page.setContent('<div data-testid=Hey>Hi</div>');
|
||||
expect(await page.evaluate(`playwright.locator('div').and(playwright.getByTestId('Hey')).elements.map(e => e.innerHTML)`)).toEqual(['Hi']);
|
||||
});
|
||||
|
||||
it('should support locator.or()', async ({ page }) => {
|
||||
await page.setContent('<div>Hi</div><span>Hello</span>');
|
||||
expect(await page.evaluate(`playwright.locator('div').or(playwright.locator('span')).elements.map(e => e.innerHTML)`)).toEqual(['Hi', 'Hello']);
|
||||
});
|
||||
|
||||
it('should support playwright.getBy*', async ({ page }) => {
|
||||
await page.setContent('<span>Hello</span><span title="world">World</span><div>one</div><div style="display:none">two</div>');
|
||||
expect(await page.evaluate(`playwright.getByText('hello').element.innerHTML`)).toContain('Hello');
|
||||
expect(await page.evaluate(`playwright.getByTitle('world').element.innerHTML`)).toContain('World');
|
||||
expect(await page.evaluate(`playwright.locator('span').filter({ hasText: 'hello' }).element.innerHTML`)).toContain('Hello');
|
||||
expect(await page.evaluate(`playwright.locator('span').first().element.innerHTML`)).toContain('Hello');
|
||||
expect(await page.evaluate(`playwright.locator('span').last().element.innerHTML`)).toContain('World');
|
||||
expect(await page.evaluate(`playwright.locator('span').nth(1).element.innerHTML`)).toContain('World');
|
||||
expect(await page.evaluate(`playwright.locator('div').filter({ visible: false }).element.innerHTML`)).toContain('two');
|
||||
});
|
||||
|
||||
it('expected properties on playwright object', async ({ page }) => {
|
||||
expect(await page.evaluate(`Object.keys(playwright)`)).toEqual([
|
||||
'$',
|
||||
'$$',
|
||||
'inspect',
|
||||
'selector',
|
||||
'generateLocator',
|
||||
'ariaSnapshot',
|
||||
'resume',
|
||||
'locator',
|
||||
'getByTestId',
|
||||
'getByAltText',
|
||||
'getByLabel',
|
||||
'getByPlaceholder',
|
||||
'getByText',
|
||||
'getByTitle',
|
||||
'getByRole',
|
||||
]);
|
||||
});
|
||||
306
참고/playwright-main/tests/library/inspector/inspectorTest.ts
Normal file
306
참고/playwright-main/tests/library/inspector/inspectorTest.ts
Normal file
@@ -0,0 +1,306 @@
|
||||
/**
|
||||
* 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 { contextTest } from '../../config/browserTest';
|
||||
import type { Locator, Page } from 'playwright-core';
|
||||
import { step } from '../../config/baseTest';
|
||||
import * as path from 'path';
|
||||
import fs from 'fs';
|
||||
import type { Source } from '../../../packages/recorder/src/recorderTypes';
|
||||
import type { CommonFixtures, TestChildProcess } from '../../config/commonFixtures';
|
||||
import { expect } from '@playwright/test';
|
||||
export { expect } from '@playwright/test';
|
||||
|
||||
type CLITestArgs = {
|
||||
recorderPageGetter: () => Promise<Page>;
|
||||
closeRecorder: () => Promise<void>;
|
||||
openRecorder: (options?: { testIdAttributeName?: string, language?: string }) => Promise<{ recorder: Recorder, page: Page }>;
|
||||
runCLI: (args: string[]) => CLIMock;
|
||||
};
|
||||
|
||||
const codegenLang2Id: Map<string, string> = new Map([
|
||||
['JSON', 'jsonl'],
|
||||
['JavaScript', 'javascript'],
|
||||
['Java', 'java'],
|
||||
['Java JUnit', 'java-junit'],
|
||||
['Python', 'python'],
|
||||
['Python Async', 'python-async'],
|
||||
['Pytest', 'python-pytest'],
|
||||
['C#', 'csharp'],
|
||||
['C# NUnit', 'csharp-nunit'],
|
||||
['C# MSTest', 'csharp-mstest'],
|
||||
['C# xUnit', 'csharp-xunit'],
|
||||
['Playwright Test', 'playwright-test'],
|
||||
]);
|
||||
const codegenLangId2lang = new Map([...codegenLang2Id.entries()].map(([lang, langId]) => [langId, lang]));
|
||||
|
||||
import { inprocess } from '../../../packages/playwright-core/lib/coreBundle';
|
||||
// Use a separate Playwright instance for automating the inspector so that
|
||||
// contexts created here do not get tracked by the test runner's tracing.
|
||||
const playwrightToAutomateInspector = inprocess.createInProcessPlaywright();
|
||||
|
||||
export const test = contextTest.extend<CLITestArgs>({
|
||||
recorderPageGetter: async ({ context, toImpl, mode, headless }, run, testInfo) => {
|
||||
testInfo.skip(mode !== 'default');
|
||||
testInfo.skip(!headless, 'real mouse moves mess up with recording');
|
||||
await run(async () => {
|
||||
while (!toImpl(context).recorderAppForTest)
|
||||
await new Promise(f => setTimeout(f, 100));
|
||||
const wsEndpoint = toImpl(context).recorderAppForTest.wsEndpointForTest;
|
||||
const browser = await playwrightToAutomateInspector.chromium.connectOverCDP({ wsEndpoint });
|
||||
const c = browser.contexts()[0];
|
||||
return c.pages()[0] || await c.waitForEvent('page');
|
||||
});
|
||||
},
|
||||
|
||||
closeRecorder: async ({ context, toImpl }, run) => {
|
||||
await run(async () => {
|
||||
await toImpl(context).recorderAppForTest.close();
|
||||
});
|
||||
},
|
||||
|
||||
runCLI: async ({ childProcess, browserName, channel, headless, mode, launchOptions }, run, testInfo) => {
|
||||
testInfo.slow();
|
||||
testInfo.skip(mode.startsWith('service'));
|
||||
|
||||
let cli: CLIMock | undefined;
|
||||
await run(cliArgs => {
|
||||
const outputFile = testInfo.outputPath('codegen.output');
|
||||
cli = new CLIMock(childProcess, {
|
||||
outputFile,
|
||||
browserName,
|
||||
channel,
|
||||
headless,
|
||||
args: cliArgs,
|
||||
executablePath: launchOptions.executablePath,
|
||||
});
|
||||
return cli;
|
||||
});
|
||||
await cli?.exit();
|
||||
},
|
||||
|
||||
openRecorder: async ({ context, recorderPageGetter }, use) => {
|
||||
await use(async options => {
|
||||
await (context as any)._enableRecorder({
|
||||
mode: 'recording',
|
||||
omitCallTracking: true,
|
||||
...options
|
||||
});
|
||||
const page = await context.newPage();
|
||||
return { page, recorder: new Recorder(page, await recorderPageGetter()) };
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export class Recorder {
|
||||
page: Page;
|
||||
_highlightCallback: Function;
|
||||
_highlightInstalled: boolean;
|
||||
_actionReporterInstalled: boolean;
|
||||
_actionPerformedCallback: Function;
|
||||
recorderPage: Page;
|
||||
private _sources = new Map<string, Source>();
|
||||
|
||||
constructor(page: Page, recorderPage: Page) {
|
||||
this.page = page;
|
||||
this.recorderPage = recorderPage;
|
||||
this._highlightCallback = () => { };
|
||||
this._highlightInstalled = false;
|
||||
this._actionReporterInstalled = false;
|
||||
this._actionPerformedCallback = () => { };
|
||||
}
|
||||
|
||||
async setContentAndWait(content: string, url: string = 'about:blank', frameCount: number = 1) {
|
||||
await this.setPageContentAndWait(this.page, content, url, frameCount);
|
||||
}
|
||||
|
||||
async setPageContentAndWait(page: Page, content: string, url: string = 'about:blank', frameCount: number = 1) {
|
||||
let callback;
|
||||
const result = new Promise(f => callback = f);
|
||||
let msgCount = 0;
|
||||
const listener = msg => {
|
||||
if (msg.text() === 'Recorder script ready for test') {
|
||||
++msgCount;
|
||||
if (msgCount === frameCount) {
|
||||
page.off('console', listener);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
page.on('console', listener);
|
||||
await page.goto(url);
|
||||
await Promise.all([
|
||||
result,
|
||||
page.setContent(content)
|
||||
]);
|
||||
}
|
||||
|
||||
async waitForOutput(file: string, text: string): Promise<Map<string, Source>> {
|
||||
return await test.step('waitForOutput', async () => {
|
||||
if (!codegenLang2Id.has(file))
|
||||
throw new Error(`Unknown language: ${file}`);
|
||||
await expect.poll(() => this.recorderPage.evaluate(languageId => {
|
||||
const sources = ((window as any).playwrightSourcesEchoForTest || []) as Source[];
|
||||
return sources.find(s => s.id === languageId)?.text || '';
|
||||
}, codegenLang2Id.get(file)), { timeout: 0 }).toContain(text);
|
||||
const sources: Source[] = await this.recorderPage.evaluate(() => (window as any).playwrightSourcesEchoForTest || []);
|
||||
for (const source of sources) {
|
||||
if (!codegenLangId2lang.has(source.id))
|
||||
throw new Error(`Unknown language: ${source.id}`);
|
||||
this._sources.set(codegenLangId2lang.get(source.id), source);
|
||||
}
|
||||
return this._sources;
|
||||
}, { box: true });
|
||||
}
|
||||
|
||||
sources(): Map<string, Source> {
|
||||
return this._sources;
|
||||
}
|
||||
|
||||
async text(file: string): Promise<string> {
|
||||
const sources: Source[] = await this.recorderPage.evaluate(() => (window as any).playwrightSourcesEchoForTest || []);
|
||||
for (const source of sources) {
|
||||
if (codegenLangId2lang.get(source.id) === file)
|
||||
return source.text;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
async waitForHighlight(action: () => Promise<void>): Promise<string> {
|
||||
return await test.step('waitForHighlight', async () => {
|
||||
await this.page.$$eval('x-pw-highlight', els => els.forEach(e => e.remove()));
|
||||
await this.page.$$eval('x-pw-tooltip', els => els.forEach(e => e.remove()));
|
||||
await action();
|
||||
await this.page.locator('x-pw-highlight').waitFor();
|
||||
await this.page.locator('x-pw-tooltip').waitFor();
|
||||
await expect(this.page.locator('x-pw-tooltip')).not.toHaveText('');
|
||||
await expect(this.page.locator('x-pw-tooltip')).not.toHaveText(`locator('body')`);
|
||||
return this.page.locator('x-pw-tooltip').textContent();
|
||||
}, { box: true });
|
||||
}
|
||||
|
||||
async waitForHighlightNoTooltip(action: () => Promise<void>): Promise<string> {
|
||||
await this.page.$$eval('x-pw-highlight', els => els.forEach(e => e.remove()));
|
||||
await action();
|
||||
await this.page.locator('x-pw-highlight').waitFor();
|
||||
return '';
|
||||
}
|
||||
|
||||
async waitForActionPerformed(): Promise<{ hovered: string | null, active: string | null }> {
|
||||
let callback;
|
||||
const listener = async msg => {
|
||||
const prefix = 'Action performed for test: ';
|
||||
if (msg.text().startsWith(prefix)) {
|
||||
this.page.off('console', listener);
|
||||
const arg = JSON.parse(msg.text().substr(prefix.length));
|
||||
callback(arg);
|
||||
}
|
||||
};
|
||||
this.page.on('console', listener);
|
||||
return new Promise(f => callback = f);
|
||||
}
|
||||
|
||||
async hoverOverElement(selector: string, options?: { position?: { x: number, y: number }, omitTooltip?: boolean }): Promise<string> {
|
||||
return (options?.omitTooltip ? this.waitForHighlightNoTooltip : this.waitForHighlight).call(this, async () => {
|
||||
const box = await this.page.locator(selector).first().boundingBox();
|
||||
const offset = options?.position || { x: box.width / 2, y: box.height / 2 };
|
||||
await this.page.mouse.move(box.x + offset.x, box.y + offset.y);
|
||||
});
|
||||
}
|
||||
|
||||
async trustedMove(selector: string | Locator) {
|
||||
const locator = typeof selector === 'string' ? this.page.locator(selector).first() : selector;
|
||||
const box = await locator.boundingBox();
|
||||
await this.page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||
}
|
||||
|
||||
async trustedClick(options?: { button?: 'left' | 'right' | 'middle' }) {
|
||||
await this.page.mouse.down(options);
|
||||
await this.page.mouse.up(options);
|
||||
}
|
||||
|
||||
async trustedPress(text: string) {
|
||||
await this.page.keyboard.press(text);
|
||||
}
|
||||
|
||||
async trustedDblclick() {
|
||||
await this.page.mouse.down();
|
||||
await this.page.mouse.up();
|
||||
await this.page.mouse.down({ clickCount: 2 });
|
||||
await this.page.mouse.up();
|
||||
}
|
||||
|
||||
async focusElement(selector: string): Promise<string> {
|
||||
return this.waitForHighlight(() => this.page.focus(selector));
|
||||
}
|
||||
}
|
||||
|
||||
class CLIMock {
|
||||
private _process: TestChildProcess;
|
||||
private _outputFile: string;
|
||||
private _exitPromise: Promise<void> | undefined;
|
||||
|
||||
constructor(childProcess: CommonFixtures['childProcess'], options: { outputFile: string, browserName: string, channel: string | undefined, headless: boolean | undefined, args: string[], executablePath: string | undefined }) {
|
||||
this._outputFile = options.outputFile;
|
||||
const nodeArgs = [
|
||||
'node',
|
||||
path.join(__dirname, '..', '..', '..', 'packages', 'playwright-core', 'cli.js'),
|
||||
'codegen',
|
||||
...options.args,
|
||||
`--browser=${options.browserName}`,
|
||||
`--output=${this._outputFile}`,
|
||||
];
|
||||
if (options.channel)
|
||||
nodeArgs.push(`--channel=${options.channel}`);
|
||||
this._process = childProcess({
|
||||
command: nodeArgs,
|
||||
env: {
|
||||
PWTEST_CLI_IS_UNDER_TEST: '1',
|
||||
PWTEST_CLI_HEADLESS: options.headless ? '1' : undefined,
|
||||
PWTEST_CLI_EXECUTABLE_PATH: options.executablePath,
|
||||
DEBUG: (process.env.DEBUG ?? '') + ',pw:browser*',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@step
|
||||
async waitFor(text: string): Promise<void> {
|
||||
await expect.poll(() => this.text(), { timeout: 30000 }).toContain(text);
|
||||
}
|
||||
|
||||
@step
|
||||
async exit() {
|
||||
if (!this._exitPromise) {
|
||||
this._process.write('exit\n');
|
||||
this._exitPromise = this._process.cleanExit();
|
||||
}
|
||||
await this._exitPromise;
|
||||
}
|
||||
|
||||
sigint() {
|
||||
const result = this._process.kill('SIGINT');
|
||||
this._exitPromise = this._process.exited.then(() => undefined); // Avoid double closing.
|
||||
return result;
|
||||
}
|
||||
|
||||
async text() {
|
||||
try {
|
||||
return await fs.promises.readFile(this._outputFile, 'utf-8');
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
21
참고/playwright-main/tests/library/inspector/pause-helper.ts
Normal file
21
참고/playwright-main/tests/library/inspector/pause-helper.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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 { Page } from 'playwright-core';
|
||||
|
||||
export async function pauseHelper(page: Page) {
|
||||
await page.setContent('<div>here we go</div>');
|
||||
}
|
||||
589
참고/playwright-main/tests/library/inspector/pause.spec.ts
Normal file
589
참고/playwright-main/tests/library/inspector/pause.spec.ts
Normal file
@@ -0,0 +1,589 @@
|
||||
/**
|
||||
* 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 { Page } from 'playwright-core';
|
||||
import { test as it, expect, Recorder } from './inspectorTest';
|
||||
import { roundBox, waitForTestLog } from '../../config/utils';
|
||||
import type { BoundingBox } from '../../config/utils';
|
||||
import { pauseHelper } from './pause-helper';
|
||||
|
||||
it('should resume when closing inspector', async ({ page, recorderPageGetter, closeRecorder, mode }) => {
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
})();
|
||||
await recorderPageGetter();
|
||||
await closeRecorder();
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should not reset timeouts', async ({ page, recorderPageGetter, closeRecorder, server }) => {
|
||||
page.context().setDefaultNavigationTimeout(1000);
|
||||
page.context().setDefaultTimeout(1000);
|
||||
|
||||
// @ts-ignore
|
||||
const pausePromise = page.pause({ __testHookKeepTestTimeout: true });
|
||||
await recorderPageGetter();
|
||||
await closeRecorder();
|
||||
await pausePromise;
|
||||
|
||||
server.setRoute('/empty.html', () => {});
|
||||
const error = await page.goto(server.EMPTY_PAGE).catch(e => e);
|
||||
expect(error.message).toContain('page.goto: Timeout 1000ms exceeded.');
|
||||
});
|
||||
|
||||
it('should collapse log entries to a single line', async ({ page, recorderPageGetter }) => {
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await page.keyboard.type(`Hello
|
||||
world`);
|
||||
})();
|
||||
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await expect(recorderPage.locator('.call-log-call').nth(1)).toContainText('Type "Hello\\nworld"');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it.describe('pause', () => {
|
||||
it.afterEach(async ({ recorderPageGetter }, testInfo) => {
|
||||
if (testInfo.status === 'skipped')
|
||||
return;
|
||||
try {
|
||||
const recorderPage = await recorderPageGetter();
|
||||
recorderPage.click('[title="Resume (F8)"]').catch(() => {});
|
||||
} catch (e) {
|
||||
// Some tests close context.
|
||||
}
|
||||
});
|
||||
|
||||
it('should pause and resume the script', async ({ page, recorderPageGetter }) => {
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should pause and resume the script with keyboard shortcut', async ({ page, recorderPageGetter }) => {
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await expect(recorderPage.getByRole('button', { name: 'Resume' })).toBeEnabled();
|
||||
await recorderPage.keyboard.press('F8');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should resume from console', async ({ page, mode }) => {
|
||||
it.skip(mode !== 'default');
|
||||
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
})();
|
||||
await page.waitForFunction(() => (window as any).playwright && (window as any).playwright.resume() !== false);
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should pause after a navigation', async ({ page, server, recorderPageGetter }) => {
|
||||
const scriptPromise = (async () => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should show source', async ({ page, recorderPageGetter }) => {
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await pauseHelper(page);
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await expect(recorderPage.getByRole('combobox', { name: 'Source chooser' })).toHaveValue(/pause\.spec\.ts/);
|
||||
await expect(recorderPage.locator('.source-line-paused')).toContainText('page.pause({ __testHookKeepTestTimeout: true })');
|
||||
|
||||
await recorderPage.click('[title="Step over (F10)"]');
|
||||
await expect(recorderPage.getByRole('combobox', { name: 'Source chooser' })).toHaveValue(/pause-helper\.ts/);
|
||||
await expect(recorderPage.locator('.source-line-paused')).toContainText('page.setContent(\'<div>here we go</div>\')');
|
||||
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should pause on next pause', async ({ page, recorderPageGetter }) => {
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true }); // 1
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true }); // 2
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
const source = await recorderPage.textContent('.source-line-paused');
|
||||
expect(source).toContain('page.pause({ __testHookKeepTestTimeout: true }); // 1');
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause({ __testHookKeepTestTimeout: true }); // 2")');
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should step', async ({ page, recorderPageGetter }) => {
|
||||
await page.setContent('<button>Submit</button>');
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await page.click('button');
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
const source = await recorderPage.textContent('.source-line-paused');
|
||||
expect(source).toContain('page.pause({ __testHookKeepTestTimeout: true });');
|
||||
|
||||
await recorderPage.click('[title="Step over (F10)"]');
|
||||
await recorderPage.waitForSelector('.source-line-paused :has-text("page.click")');
|
||||
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should disable timeout on paused actions', async ({ page, recorderPageGetter }) => {
|
||||
await page.setContent('<button>Submit</button>');
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await page.click('button', { timeout: 1000 });
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
const source = await recorderPage.textContent('.source-line-paused');
|
||||
expect(source).toContain('page.pause({ __testHookKeepTestTimeout: true });');
|
||||
|
||||
await recorderPage.click('[title="Step over (F10)"]');
|
||||
await recorderPage.waitForSelector('.source-line-paused :has-text("page.click")');
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should step with keyboard shortcut', async ({ page, recorderPageGetter }) => {
|
||||
await page.setContent('<button>Submit</button>');
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await page.click('button');
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
const source = await recorderPage.textContent('.source-line-paused');
|
||||
expect(source).toContain('page.pause({ __testHookKeepTestTimeout: true });');
|
||||
|
||||
await recorderPage.keyboard.press('F10');
|
||||
await recorderPage.waitForSelector('.source-line-paused :has-text("page.click")');
|
||||
await recorderPage.isEnabled('[title="Resume (F8)"]');
|
||||
|
||||
await recorderPage.keyboard.press('F8');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should highlight pointer, only in main frame', async ({ page, recorderPageGetter }) => {
|
||||
await page.setContent(`
|
||||
<iframe
|
||||
style="margin: 100px;"
|
||||
srcdoc="<button style='margin: 80px;'>Submit</button>">
|
||||
</iframe>
|
||||
`);
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await page.frameLocator('iframe').locator('button').click();
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await recorderPage.click('[title="Step over (F10)"]');
|
||||
|
||||
const { box1, box2 } = await (page as any)._wrapApiCall(async () => {
|
||||
const iframe = page.frames()[1];
|
||||
const button = await iframe.waitForSelector('button');
|
||||
const box1 = await button.boundingBox();
|
||||
|
||||
const actionPoint = await page.waitForSelector('x-pw-action-point');
|
||||
const box2 = await actionPoint.boundingBox();
|
||||
|
||||
const iframeActionPoint = await iframe.$('x-pw-action-point');
|
||||
expect(await iframeActionPoint?.isVisible()).toBeFalsy();
|
||||
|
||||
return { box1, box2 };
|
||||
}, { internal: true });
|
||||
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
|
||||
const x1 = box1!.x + box1!.width / 2;
|
||||
const y1 = box1!.y + box1!.height / 2;
|
||||
const x2 = box2!.x + box2!.width / 2;
|
||||
const y2 = box2!.y + box2!.height / 2;
|
||||
|
||||
expect(Math.abs(x1 - x2) < 2).toBeTruthy();
|
||||
expect(Math.abs(y1 - y2) < 2).toBeTruthy();
|
||||
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should skip input when resuming', async ({ page, recorderPageGetter }) => {
|
||||
await page.setContent('<button>Submit</button>');
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await page.click('button');
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true }); // 2
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause({ __testHookKeepTestTimeout: true }); // 2")');
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should populate log', async ({ page, recorderPageGetter }) => {
|
||||
await page.setContent('<button>Submit</button>');
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await page.click('button');
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true }); // 2
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause({ __testHookKeepTestTimeout: true }); // 2")');
|
||||
expect(await sanitizeLog(recorderPage)).toEqual([
|
||||
'Pause- XXms',
|
||||
'Click(page.locator(\'button\'))- XXms',
|
||||
'Pause',
|
||||
]);
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should hide internal calls', async ({ page, recorderPageGetter, trace }) => {
|
||||
it.skip(trace === 'on');
|
||||
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await page.context().tracing.start();
|
||||
page.setDefaultTimeout(0);
|
||||
page.context().setDefaultNavigationTimeout(0);
|
||||
await page.context().tracing.stop();
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true }); // 2
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause({ __testHookKeepTestTimeout: true }); // 2")');
|
||||
expect(await sanitizeLog(recorderPage)).toEqual([
|
||||
'Pause- XXms',
|
||||
'Start tracing- XXms',
|
||||
'Stop tracing- XXms',
|
||||
'Pause',
|
||||
]);
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should show expect.toHaveText', async ({ page, recorderPageGetter }) => {
|
||||
await page.setContent('<button>Submit</button>');
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await expect(page.locator('button')).toHaveText('Submit');
|
||||
await expect(page.locator('button')).not.toHaveText('Submit2');
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true }); // 2
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause({ __testHookKeepTestTimeout: true }); // 2")');
|
||||
expect(await sanitizeLog(recorderPage)).toEqual([
|
||||
'Pause- XXms',
|
||||
'Expect "toHaveText"(page.locator(\'button\'))- XXms',
|
||||
'Expect "not toHaveText"(page.locator(\'button\'))- XXms',
|
||||
'Pause',
|
||||
]);
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should highlight waitForEvent', async ({ page, recorderPageGetter }) => {
|
||||
await page.setContent('<button onclick="console.log(1)">Submit</button>');
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await Promise.all([
|
||||
page.waitForEvent('console', msg => msg.type() === 'log' && msg.text() === '1'),
|
||||
page.click('button'),
|
||||
]);
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await recorderPage.click('[title="Step over (F10)"]');
|
||||
await recorderPage.waitForSelector('.source-line-paused:has-text("page.click")');
|
||||
await recorderPage.waitForSelector('.source-line-running:has-text("page.waitForEvent")');
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should populate log with waitForEvent', async ({ page, recorderPageGetter }) => {
|
||||
await page.setContent('<button onclick="console.log(1)">Submit</button>');
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await Promise.all([
|
||||
page.waitForEvent('console'),
|
||||
page.getByRole('button', { name: 'Submit' }).click(),
|
||||
]);
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true }); // 2
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause({ __testHookKeepTestTimeout: true }); // 2")');
|
||||
expect(await sanitizeLog(recorderPage)).toEqual([
|
||||
'Pause- XXms',
|
||||
'Wait for event "console"- XXms',
|
||||
'Click(page.getByRole(\'button\', { name: \'Submit\' }))- XXms',
|
||||
'Pause',
|
||||
]);
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should populate log with error', async ({ page, recorderPageGetter }) => {
|
||||
await page.setContent('<button onclick="console.log(1)">Submit</button>');
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await page.getByRole('button').isChecked();
|
||||
})().catch(e => e);
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await recorderPage.waitForSelector('.source-line-error-underline');
|
||||
expect(await sanitizeLog(recorderPage)).toEqual([
|
||||
'Pause- XXms',
|
||||
'Is checked(page.getByRole(\'button\'))- XXms',
|
||||
'waiting for getByRole(\'button\')',
|
||||
'error: Error: Not a checkbox or radio button',
|
||||
]);
|
||||
const error = await scriptPromise;
|
||||
expect(error.message).toContain('Not a checkbox or radio button');
|
||||
});
|
||||
|
||||
it('should populate log with error in waitForEvent', async ({ page, recorderPageGetter }) => {
|
||||
await page.setContent('<button>Submit</button>');
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await Promise.all([
|
||||
page.waitForEvent('console', { timeout: 1 }).catch(() => {}),
|
||||
// @ts-ignore
|
||||
page.pause({ __testHookKeepTestTimeout: true }),
|
||||
]);
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await recorderPage.click('[title="Step over (F10)"]');
|
||||
await recorderPage.waitForSelector('.source-line-paused:has-text("page.pause")');
|
||||
await recorderPage.waitForSelector('.source-line-error:has-text("page.waitForEvent")');
|
||||
expect(await sanitizeLog(recorderPage)).toEqual([
|
||||
'Pause- XXms',
|
||||
'Wait for event "console"- XXms',
|
||||
'waiting for event "console"',
|
||||
'error: Timeout 1ms exceeded while waiting for event "console"',
|
||||
'Pause',
|
||||
]);
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should pause on page close', async ({ page, recorderPageGetter }) => {
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await page.close();
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await recorderPage.click('[title="Step over (F10)"]');
|
||||
await recorderPage.waitForSelector('.source-line-paused:has-text("page.close();")');
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should pause on context close', async ({ page, recorderPageGetter }) => {
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await page.context().close();
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await recorderPage.click('[title="Step over (F10)"]');
|
||||
await recorderPage.waitForSelector('.source-line-paused:has-text("page.context().close();")');
|
||||
// Next line can throw because closing context also closes the inspector page.
|
||||
await recorderPage.click('[title="Resume (F8)"]').catch(e => {});
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should highlight on explore', async ({ page, recorderPageGetter }) => {
|
||||
await page.setContent('<button>Submit</button>');
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
|
||||
const box1Promise = waitForTestLog<BoundingBox>(page, 'Highlight box for test: ');
|
||||
await recorderPage.getByText('Locator', { exact: true }).click();
|
||||
await recorderPage.locator('.tabbed-pane .CodeMirror').click();
|
||||
await recorderPage.keyboard.press('ControlOrMeta+A');
|
||||
await recorderPage.keyboard.press('Backspace');
|
||||
await recorderPage.keyboard.type('getByText(\'Submit\')');
|
||||
const box1 = await box1Promise;
|
||||
|
||||
const button = await page.$('text=Submit');
|
||||
const box2 = await button!.boundingBox();
|
||||
expect(roundBox(box1)).toEqual(roundBox(box2!));
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should highlight on explore (csharp)', async ({ openRecorder }) => {
|
||||
process.env.TEST_INSPECTOR_LANGUAGE = 'csharp';
|
||||
try {
|
||||
const { page, recorder } = await openRecorder();
|
||||
await page.setContent('<button>Submit</button>');
|
||||
|
||||
const box1Promise = waitForTestLog<BoundingBox>(page, 'Highlight box for test: ');
|
||||
await recorder.recorderPage.getByText('Locator', { exact: true }).click();
|
||||
await recorder.recorderPage.locator('.tabbed-pane .CodeMirror').click();
|
||||
await recorder.recorderPage.keyboard.press('ControlOrMeta+A');
|
||||
await recorder.recorderPage.keyboard.press('Backspace');
|
||||
await recorder.recorderPage.keyboard.type('GetByText("Submit")');
|
||||
const box1 = await box1Promise;
|
||||
|
||||
const button = await page.$('text=Submit');
|
||||
const box2 = await button.boundingBox();
|
||||
expect(roundBox(box1)).toEqual(roundBox(box2));
|
||||
} finally {
|
||||
delete process.env.TEST_INSPECTOR_LANGUAGE;
|
||||
}
|
||||
});
|
||||
|
||||
it('should not prevent key events', async ({ page, recorderPageGetter }) => {
|
||||
await page.setContent('<div>Hello</div>');
|
||||
await page.evaluate(() => {
|
||||
(window as any).log = [];
|
||||
for (const event of ['keydown', 'keyup', 'keypress'])
|
||||
window.addEventListener(event, e => (window as any).log.push(e.type));
|
||||
});
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
await page.keyboard.press('Enter');
|
||||
await page.keyboard.press('A');
|
||||
await page.keyboard.press('Shift+A');
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await recorderPage.waitForSelector(`.source-line-paused:has-text("page.pause")`);
|
||||
await recorderPage.click('[title="Step over (F10)"]');
|
||||
await recorderPage.waitForSelector(`.source-line-paused:has-text("press('Enter')")`);
|
||||
await recorderPage.click('[title="Step over (F10)"]');
|
||||
await recorderPage.waitForSelector(`.source-line-paused:has-text("press('A')")`);
|
||||
await recorderPage.click('[title="Step over (F10)"]');
|
||||
await recorderPage.waitForSelector(`.source-line-paused:has-text("press('Shift+A')")`);
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
|
||||
const log = await page.evaluate(() => (window as any).log);
|
||||
expect(log).toEqual([
|
||||
'keydown',
|
||||
'keypress',
|
||||
'keyup',
|
||||
'keydown',
|
||||
'keypress',
|
||||
'keyup',
|
||||
'keydown',
|
||||
'keydown',
|
||||
'keypress',
|
||||
'keyup',
|
||||
'keyup',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should highlight locators with custom testId', async ({ page, playwright, recorderPageGetter }) => {
|
||||
await page.setContent('<div data-custom-id=foo id=target>and me</div>');
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
playwright.selectors.setTestIdAttribute('data-custom-id');
|
||||
await page.getByTestId('foo').click();
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
|
||||
const box1Promise = waitForTestLog<BoundingBox>(page, 'Highlight box for test: ');
|
||||
await recorderPage.click('[title="Step over (F10)"]');
|
||||
const box2 = roundBox((await page.locator('#target').boundingBox())!);
|
||||
const box1 = roundBox(await box1Promise);
|
||||
expect(box1).toEqual(box2);
|
||||
|
||||
await recorderPage.click('[title="Resume (F8)"]');
|
||||
await scriptPromise;
|
||||
});
|
||||
|
||||
it('should record from debugger', async ({ page, recorderPageGetter }) => {
|
||||
await page.setContent('<body style="width: 100%; height: 100%"></body>');
|
||||
const scriptPromise = (async () => {
|
||||
// @ts-ignore
|
||||
await page.pause({ __testHookKeepTestTimeout: true });
|
||||
})();
|
||||
const recorderPage = await recorderPageGetter();
|
||||
await expect(recorderPage.getByRole('combobox', { name: 'Source chooser' })).toHaveValue(/pause\.spec\.ts/);
|
||||
await expect(recorderPage.locator('.source-line-paused')).toHaveText(/await page\.pause\(.*\)/);
|
||||
await recorderPage.getByRole('button', { name: 'Record' }).click();
|
||||
|
||||
const recorder = new Recorder(page, recorderPage);
|
||||
await recorder.hoverOverElement('body', { omitTooltip: true });
|
||||
await recorder.trustedClick();
|
||||
|
||||
await expect(recorderPage.getByRole('combobox', { name: 'Source chooser' })).toHaveValue('playwright-test');
|
||||
await expect(recorderPage.locator('.cm-wrapper')).toContainText(`await page.locator('body').click();`);
|
||||
await recorderPage.getByRole('button', { name: 'Resume' }).click();
|
||||
await scriptPromise;
|
||||
});
|
||||
});
|
||||
|
||||
async function sanitizeLog(recorderPage: Page): Promise<string[]> {
|
||||
const results = [];
|
||||
for (const entry of await recorderPage.$$('.call-log-call')) {
|
||||
const header = (await (await entry.$('.call-log-call-header'))!.textContent())!.replace(/— [\d.]+(ms|s)/, '- XXms');
|
||||
results.push(header.replace(/page\.waitForEvent\(console\).*/, 'page.waitForEvent(console)'));
|
||||
results.push(...await entry.$$eval('.call-log-message', ee => ee.map(e => {
|
||||
return (e.classList.contains('error') ? 'error: ' : '') + e.textContent;
|
||||
})));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
192
참고/playwright-main/tests/library/inspector/recorder-api.spec.ts
Normal file
192
참고/playwright-main/tests/library/inspector/recorder-api.spec.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* 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 { test, expect } from './inspectorTest';
|
||||
|
||||
import type { Page } from '@playwright/test';
|
||||
import type * as actions from '@recorder/actions';
|
||||
|
||||
class RecorderLog {
|
||||
actions: (actions.ActionInContext & { code: string })[] = [];
|
||||
|
||||
actionAdded(page: Page, actionInContext: actions.ActionInContext, code: string): void {
|
||||
this.actions.push({ ...actionInContext, code });
|
||||
}
|
||||
|
||||
actionUpdated(page: Page, actionInContext: actions.ActionInContext, code: string): void {
|
||||
this.actions[this.actions.length - 1] = { ...actionInContext, code };
|
||||
}
|
||||
}
|
||||
|
||||
async function startRecording(context) {
|
||||
const log = new RecorderLog();
|
||||
await (context as any)._enableRecorder({
|
||||
mode: 'recording',
|
||||
recorderMode: 'api',
|
||||
}, log);
|
||||
return {
|
||||
action: (name: string) => log.actions.filter(a => a.action.name === name),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeCode(code: string): string {
|
||||
return code.replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
test('should click', async ({ context, browserName, platform, channel }) => {
|
||||
const log = await startRecording(context);
|
||||
const page = await context.newPage();
|
||||
await page.setContent(`<button onclick="console.log('click')">Submit</button>`);
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
|
||||
const clickActions = log.action('click');
|
||||
expect(clickActions).toEqual([
|
||||
expect.objectContaining({
|
||||
action: expect.objectContaining({
|
||||
name: 'click',
|
||||
selector: 'internal:role=button[name="Submit"i]',
|
||||
ref: 'e2',
|
||||
// Safari does not focus after a click: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#clicking_and_focus
|
||||
ariaSnapshot: (browserName === 'webkit' && (platform === 'darwin' || (platform === 'win32' && channel !== 'webkit-wsl'))) ? '- button "Submit" [ref=e2]' : '- button "Submit" [active] [ref=e2]',
|
||||
}),
|
||||
startTime: expect.any(Number),
|
||||
})
|
||||
]);
|
||||
|
||||
expect(normalizeCode(clickActions[0].code)).toEqual(`await page.getByRole('button', { name: 'Submit' }).click();`);
|
||||
});
|
||||
|
||||
test('should double click', async ({ context, browserName, platform, channel }) => {
|
||||
const log = await startRecording(context);
|
||||
const page = await context.newPage();
|
||||
await page.setContent(`<button onclick="console.log('click')" ondblclick="console.log('dblclick')">Submit</button>`);
|
||||
await page.getByRole('button', { name: 'Submit' }).dblclick();
|
||||
|
||||
const clickActions = log.action('click');
|
||||
expect(clickActions).toEqual([
|
||||
expect.objectContaining({
|
||||
action: expect.objectContaining({
|
||||
name: 'click',
|
||||
clickCount: 2,
|
||||
selector: 'internal:role=button[name="Submit"i]',
|
||||
ref: 'e2',
|
||||
// Safari does not focus after a click: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#clicking_and_focus
|
||||
ariaSnapshot: (browserName === 'webkit' && (platform === 'darwin' || (platform === 'win32' && channel !== 'webkit-wsl'))) ? '- button "Submit" [ref=e2]' : '- button "Submit" [active] [ref=e2]',
|
||||
}),
|
||||
startTime: expect.any(Number),
|
||||
})
|
||||
]);
|
||||
|
||||
expect(normalizeCode(clickActions[0].code)).toEqual(`await page.getByRole('button', { name: 'Submit' }).dblclick();`);
|
||||
});
|
||||
|
||||
test('should right click', async ({ context, browserName, platform, channel }) => {
|
||||
const log = await startRecording(context);
|
||||
const page = await context.newPage();
|
||||
await page.setContent(`<button oncontextmenu="console.log('contextmenu')">Submit</button>`);
|
||||
await page.getByRole('button', { name: 'Submit' }).click({ button: 'right' });
|
||||
|
||||
const clickActions = log.action('click');
|
||||
expect(clickActions).toEqual([
|
||||
expect.objectContaining({
|
||||
action: expect.objectContaining({
|
||||
name: 'click',
|
||||
button: 'right',
|
||||
selector: 'internal:role=button[name="Submit"i]',
|
||||
ref: 'e2',
|
||||
// Safari does not focus after a click: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#clicking_and_focus
|
||||
ariaSnapshot: (browserName === 'webkit' && (platform === 'darwin' || (platform === 'win32' && channel !== 'webkit-wsl'))) ? '- button "Submit" [ref=e2]' : '- button "Submit" [active] [ref=e2]',
|
||||
}),
|
||||
startTime: expect.any(Number),
|
||||
})
|
||||
]);
|
||||
|
||||
expect(normalizeCode(clickActions[0].code)).toEqual(`await page.getByRole('button', { name: 'Submit' }).click({ button: 'right' });`);
|
||||
});
|
||||
|
||||
test('should type', async ({ context }) => {
|
||||
const log = await startRecording(context);
|
||||
const page = await context.newPage();
|
||||
await page.setContent(`<input type="text" />`);
|
||||
|
||||
await page.getByRole('textbox').pressSequentially('Hello');
|
||||
|
||||
const fillActions = log.action('fill');
|
||||
expect(fillActions).toEqual([
|
||||
expect.objectContaining({
|
||||
action: expect.objectContaining({
|
||||
name: 'fill',
|
||||
selector: 'internal:role=textbox',
|
||||
ref: 'e2',
|
||||
ariaSnapshot: '- textbox [active] [ref=e2]: Hello',
|
||||
}),
|
||||
startTime: expect.any(Number),
|
||||
})
|
||||
]);
|
||||
|
||||
expect(normalizeCode(fillActions[0].code)).toEqual(`await page.getByRole('textbox').fill('Hello');`);
|
||||
});
|
||||
|
||||
test('should disable recorder', async ({ context }) => {
|
||||
const log = await startRecording(context);
|
||||
const page = await context.newPage();
|
||||
await page.setContent(`<button onclick="console.log('click')">Submit</button>`);
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
expect(log.action('click')).toHaveLength(2);
|
||||
await (context as any)._disableRecorder();
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
expect(log.action('click')).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('page.pickLocator should return locator for picked element', async ({ page }) => {
|
||||
await page.setContent(`<button>Submit</button>`);
|
||||
|
||||
const scriptReady = page.waitForEvent('console', msg => msg.text() === 'Recorder script ready for test');
|
||||
const pickPromise = page.pickLocator();
|
||||
await scriptReady;
|
||||
|
||||
const box = await page.getByRole('button', { name: 'Submit' }).boundingBox();
|
||||
await page.mouse.click(box!.x + box!.width / 2, box!.y + box!.height / 2);
|
||||
|
||||
const locator = await pickPromise;
|
||||
await expect(locator).toHaveText('Submit');
|
||||
});
|
||||
|
||||
test('page.cancelPickLocator should cancel ongoing pickLocator', async ({ page }) => {
|
||||
const pickPromise = page.pickLocator();
|
||||
await Promise.all([
|
||||
page.cancelPickLocator(),
|
||||
expect(pickPromise).rejects.toThrow('Locator picking was cancelled')
|
||||
]);
|
||||
});
|
||||
|
||||
test('closing page should cancel ongoing pickLocator', async ({ page }) => {
|
||||
await page.setContent(`<button>Click me</button>`);
|
||||
const pickPromise = page.pickLocator().catch(e => e.message);
|
||||
await page.close();
|
||||
expect(await pickPromise).toContain('Target page, context or browser has been closed');
|
||||
});
|
||||
|
||||
test('page2.pickLocator() should cancel page1.pickLocator()', async ({ page, context, browserName, headless, isMac, macVersion }) => {
|
||||
test.fixme(browserName === 'chromium' && !headless && isMac && macVersion === 14, 'times out on chromium headed on macOS 14');
|
||||
const pick1Promise = page.pickLocator().catch(e => e.message);
|
||||
|
||||
const page2 = await context.newPage();
|
||||
page2.pickLocator().catch(() => {});
|
||||
|
||||
expect(await pick1Promise).toContain('Locator picking was cancelled');
|
||||
});
|
||||
65
참고/playwright-main/tests/library/inspector/title.spec.ts
Normal file
65
참고/playwright-main/tests/library/inspector/title.spec.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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 { test, expect } from './inspectorTest';
|
||||
|
||||
test('should reflect formatted URL of the page', async ({
|
||||
openRecorder,
|
||||
server,
|
||||
}) => {
|
||||
const { recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait('');
|
||||
await expect(recorder.recorderPage).toHaveTitle(
|
||||
'Playwright Inspector - about:blank',
|
||||
);
|
||||
|
||||
await recorder.setContentAndWait('', server.EMPTY_PAGE);
|
||||
await expect(recorder.recorderPage).toHaveTitle(
|
||||
`Playwright Inspector - ${server.EMPTY_PAGE}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('should update primary page URL when original primary closes', async ({
|
||||
context,
|
||||
openRecorder,
|
||||
server,
|
||||
}) => {
|
||||
const { recorder } = await openRecorder();
|
||||
await recorder.setContentAndWait(
|
||||
'',
|
||||
`${server.PREFIX}/background-color.html`,
|
||||
);
|
||||
await expect(recorder.recorderPage).toHaveTitle(
|
||||
`Playwright Inspector - ${server.PREFIX}/background-color.html`,
|
||||
);
|
||||
|
||||
const page2 = await context.newPage();
|
||||
await page2.goto(`${server.PREFIX}/dom.html`);
|
||||
await expect(recorder.recorderPage).toHaveTitle(
|
||||
`Playwright Inspector - ${server.PREFIX}/background-color.html`,
|
||||
);
|
||||
|
||||
await context.pages()[0].close();
|
||||
await expect(recorder.recorderPage).toHaveTitle(
|
||||
`Playwright Inspector - ${server.PREFIX}/dom.html`,
|
||||
);
|
||||
});
|
||||
|
||||
test('should render primary language', async ({ openRecorder }) => {
|
||||
const { recorder } = await openRecorder({ language: 'python' });
|
||||
await recorder.setContentAndWait('');
|
||||
await expect(recorder.recorderPage.getByRole('combobox', { name: 'Source chooser' })).toHaveValue('python');
|
||||
});
|
||||
Reference in New Issue
Block a user