Merge branch 'microsoft:main' into main
This commit is contained in:
commit
2f91918256
|
|
@ -601,6 +601,23 @@ steps:
|
||||||
- 'CI=true'
|
- 'CI=true'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Drone
|
||||||
|
* langs: js
|
||||||
|
|
||||||
|
To run Playwright tests on Drone, use our public Docker image ([see Dockerfile](./docker.md)).
|
||||||
|
|
||||||
|
```yml
|
||||||
|
kind: pipeline
|
||||||
|
name: default
|
||||||
|
type: docker
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: test
|
||||||
|
image: mcr.microsoft.com/playwright:v%%VERSION%%-jammy
|
||||||
|
commands:
|
||||||
|
- npx playwright test
|
||||||
|
```
|
||||||
|
|
||||||
## Caching browsers
|
## Caching browsers
|
||||||
|
|
||||||
Caching browser binaries is not recommended, since the amount of time it takes to restore the cache is comparable to the time it takes to download the binaries. Especially under Linux, [operating system dependencies](./browsers.md#install-system-dependencies) need to be installed, which are not cacheable.
|
Caching browser binaries is not recommended, since the amount of time it takes to restore the cache is comparable to the time it takes to download the binaries. Especially under Linux, [operating system dependencies](./browsers.md#install-system-dependencies) need to be installed, which are not cacheable.
|
||||||
|
|
@ -633,7 +650,7 @@ By default, Playwright launches browsers in headless mode. See in our [Running t
|
||||||
On Linux agents, headed execution requires [Xvfb](https://en.wikipedia.org/wiki/Xvfb) to be installed. Our [Docker image](./docker.md) and GitHub Action have Xvfb pre-installed. To run browsers in headed mode with Xvfb, add `xvfb-run` before the actual command.
|
On Linux agents, headed execution requires [Xvfb](https://en.wikipedia.org/wiki/Xvfb) to be installed. Our [Docker image](./docker.md) and GitHub Action have Xvfb pre-installed. To run browsers in headed mode with Xvfb, add `xvfb-run` before the actual command.
|
||||||
|
|
||||||
```bash js
|
```bash js
|
||||||
xvfb-run npx playwrght test
|
xvfb-run npx playwright test
|
||||||
```
|
```
|
||||||
```bash python
|
```bash python
|
||||||
xvfb-run pytest
|
xvfb-run pytest
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@ Utilizing the new [Clock] API allows to manipulate and control time within tests
|
||||||
```csharp
|
```csharp
|
||||||
// Initialize clock with some time before the test time and let the page load naturally.
|
// Initialize clock with some time before the test time and let the page load naturally.
|
||||||
// `Date.now` will progress as the timers fire.
|
// `Date.now` will progress as the timers fire.
|
||||||
await Page.Clock.InstallAsync(new
|
await Page.Clock.InstallAsync(new()
|
||||||
{
|
{
|
||||||
Time = new DateTime(2024, 2, 2, 8, 0, 0)
|
TimeDate = new DateTime(2024, 2, 2, 8, 0, 0)
|
||||||
});
|
});
|
||||||
await Page.GotoAsync("http://localhost:3333");
|
await Page.GotoAsync("http://localhost:3333");
|
||||||
|
|
||||||
|
|
@ -28,11 +28,11 @@ await Page.GotoAsync("http://localhost:3333");
|
||||||
await Page.Clock.PauseAtAsync(new DateTime(2024, 2, 2, 10, 0, 0));
|
await Page.Clock.PauseAtAsync(new DateTime(2024, 2, 2, 10, 0, 0));
|
||||||
|
|
||||||
// Assert the page state.
|
// Assert the page state.
|
||||||
await Expect(Page.GetByTestId("current-time")).ToHaveText("2/2/2024, 10:00:00 AM");
|
await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:00:00 AM");
|
||||||
|
|
||||||
// Close the laptop lid again and open it at 10:30am.
|
// Close the laptop lid again and open it at 10:30am.
|
||||||
await Page.Clock.FastForwardAsync("30:00");
|
await Page.Clock.FastForwardAsync("30:00");
|
||||||
await Expect(Page.GetByTestId("current-time")).ToHaveText("2/2/2024, 10:30:00 AM");
|
await Expect(Page.GetByTestId("current-time")).ToHaveTextAsync("2/2/2024, 10:30:00 AM");
|
||||||
```
|
```
|
||||||
|
|
||||||
See [the clock guide](./clock.md) for more details.
|
See [the clock guide](./clock.md) for more details.
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "webkit",
|
"name": "webkit",
|
||||||
"revision": "2039",
|
"revision": "2041",
|
||||||
"installByDefault": true,
|
"installByDefault": true,
|
||||||
"revisionOverrides": {
|
"revisionOverrides": {
|
||||||
"mac10.14": "1446",
|
"mac10.14": "1446",
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import type { Page } from './page';
|
||||||
import { ConsoleMessage } from './consoleMessage';
|
import { ConsoleMessage } from './consoleMessage';
|
||||||
import type { Env, WaitForEventOptions, Headers, BrowserContextOptions } from './types';
|
import type { Env, WaitForEventOptions, Headers, BrowserContextOptions } from './types';
|
||||||
import { Waiter } from './waiter';
|
import { Waiter } from './waiter';
|
||||||
import { TargetClosedError } from './errors';
|
import { TargetClosedError, isTargetClosedError } from './errors';
|
||||||
|
|
||||||
type ElectronOptions = Omit<channels.ElectronLaunchOptions, 'env'|'extraHTTPHeaders'|'recordHar'|'colorScheme'|'acceptDownloads'> & {
|
type ElectronOptions = Omit<channels.ElectronLaunchOptions, 'env'|'extraHTTPHeaders'|'recordHar'|'colorScheme'|'acceptDownloads'> & {
|
||||||
env?: Env,
|
env?: Env,
|
||||||
|
|
@ -116,7 +116,13 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
|
||||||
}
|
}
|
||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
await this._context.close().catch(() => {});
|
try {
|
||||||
|
await this._context.close();
|
||||||
|
} catch (e) {
|
||||||
|
if (isTargetClosedError(e))
|
||||||
|
return;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
|
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@
|
||||||
"./hooks": {
|
"./hooks": {
|
||||||
"types": "./hooks.d.ts",
|
"types": "./hooks.d.ts",
|
||||||
"default": "./hooks.mjs"
|
"default": "./hooks.mjs"
|
||||||
}
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.46.0-next",
|
"@playwright/experimental-ct-core": "1.46.0-next",
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@
|
||||||
"./hooks": {
|
"./hooks": {
|
||||||
"types": "./hooks.d.ts",
|
"types": "./hooks.d.ts",
|
||||||
"default": "./hooks.mjs"
|
"default": "./hooks.mjs"
|
||||||
}
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.46.0-next",
|
"@playwright/experimental-ct-core": "1.46.0-next",
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@
|
||||||
"./hooks": {
|
"./hooks": {
|
||||||
"types": "./hooks.d.ts",
|
"types": "./hooks.d.ts",
|
||||||
"default": "./hooks.mjs"
|
"default": "./hooks.mjs"
|
||||||
}
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.46.0-next",
|
"@playwright/experimental-ct-core": "1.46.0-next",
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@
|
||||||
"./hooks": {
|
"./hooks": {
|
||||||
"types": "./hooks.d.ts",
|
"types": "./hooks.d.ts",
|
||||||
"default": "./hooks.mjs"
|
"default": "./hooks.mjs"
|
||||||
}
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.46.0-next",
|
"@playwright/experimental-ct-core": "1.46.0-next",
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@
|
||||||
"./hooks": {
|
"./hooks": {
|
||||||
"types": "./hooks.d.ts",
|
"types": "./hooks.d.ts",
|
||||||
"default": "./hooks.mjs"
|
"default": "./hooks.mjs"
|
||||||
}
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.46.0-next",
|
"@playwright/experimental-ct-core": "1.46.0-next",
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@
|
||||||
"./hooks": {
|
"./hooks": {
|
||||||
"types": "./hooks.d.ts",
|
"types": "./hooks.d.ts",
|
||||||
"default": "./hooks.mjs"
|
"default": "./hooks.mjs"
|
||||||
}
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.46.0-next",
|
"@playwright/experimental-ct-core": "1.46.0-next",
|
||||||
|
|
|
||||||
|
|
@ -609,7 +609,7 @@ class ArtifactsRecorder {
|
||||||
if ((tracing as any)[this._startedCollectingArtifacts])
|
if ((tracing as any)[this._startedCollectingArtifacts])
|
||||||
return;
|
return;
|
||||||
(tracing as any)[this._startedCollectingArtifacts] = true;
|
(tracing as any)[this._startedCollectingArtifacts] = true;
|
||||||
if (this._testInfo._tracing.traceOptions())
|
if (this._testInfo._tracing.traceOptions() && (tracing as any)[kTracingStarted])
|
||||||
await tracing.stopChunk({ path: this._testInfo._tracing.generateNextTraceRecordingPath() });
|
await tracing.stopChunk({ path: this._testInfo._tracing.generateNextTraceRecordingPath() });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
import { test } from './npmTest';
|
import { test } from './npmTest';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { expect } from 'packages/playwright-test';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
test('electron should work', async ({ exec, tsc, writeFiles }) => {
|
test('electron should work', async ({ exec, tsc, writeFiles }) => {
|
||||||
|
|
@ -39,3 +40,46 @@ test('electron should work with special characters in path', async ({ exec, tmpW
|
||||||
cwd: path.join(folderName)
|
cwd: path.join(folderName)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should work when wrapped inside @playwright/test and trace is enabled', async ({ exec, tmpWorkspace, writeFiles }) => {
|
||||||
|
await exec('npm i -D @playwright/test electron@31');
|
||||||
|
await writeFiles({
|
||||||
|
'electron-with-tracing.spec.ts': `
|
||||||
|
import { test, expect, _electron } from '@playwright/test';
|
||||||
|
|
||||||
|
test('should work', async ({ trace }) => {
|
||||||
|
const electronApp = await _electron.launch({ args: [${JSON.stringify(path.join(__dirname, '../electron/electron-window-app.js'))}] });
|
||||||
|
|
||||||
|
const window = await electronApp.firstWindow();
|
||||||
|
if (trace)
|
||||||
|
await window.context().tracing.start({ screenshots: true, snapshots: true });
|
||||||
|
|
||||||
|
await window.goto('data:text/html,<title>Playwright</title><h1>Playwright</h1>');
|
||||||
|
await expect(window).toHaveTitle(/Playwright/);
|
||||||
|
await expect(window.getByRole('heading')).toHaveText('Playwright');
|
||||||
|
|
||||||
|
const path = test.info().outputPath('electron-trace.zip');
|
||||||
|
if (trace) {
|
||||||
|
await window.context().tracing.stop({ path });
|
||||||
|
test.info().attachments.push({ name: 'trace', path, contentType: 'application/zip' });
|
||||||
|
}
|
||||||
|
await electronApp.close();
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
const jsonOutputName = test.info().outputPath('report.json');
|
||||||
|
await exec('npx playwright test --trace=on --reporter=json electron-with-tracing.spec.ts', {
|
||||||
|
env: { PLAYWRIGHT_JSON_OUTPUT_NAME: jsonOutputName }
|
||||||
|
});
|
||||||
|
const traces = [
|
||||||
|
// our actual trace.
|
||||||
|
path.join(tmpWorkspace, 'test-results', 'electron-with-tracing-should-work', 'electron-trace.zip'),
|
||||||
|
// contains the expect() calls
|
||||||
|
path.join(tmpWorkspace, 'test-results', 'electron-with-tracing-should-work', 'trace.zip'),
|
||||||
|
];
|
||||||
|
for (const trace of traces)
|
||||||
|
expect(fs.existsSync(trace)).toBe(true);
|
||||||
|
const report = JSON.parse(fs.readFileSync(jsonOutputName, 'utf-8'));
|
||||||
|
expect(new Set(['trace'])).toEqual(new Set(report.suites[0].specs[0].tests[0].results[0].attachments.map(a => a.name)));
|
||||||
|
expect(new Set(traces.map(p => fs.realpathSync(p)))).toEqual(new Set(report.suites[0].specs[0].tests[0].results[0].attachments.map(a => a.path)));
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { test as it, expect } from './pageTest';
|
import { test as it, expect } from './pageTest';
|
||||||
import { attachFrame, chromiumVersionLessThan } from '../config/utils';
|
import { attachFrame } from '../config/utils';
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
|
@ -37,7 +37,7 @@ it('should upload the file', async ({ page, server, asset }) => {
|
||||||
}, input)).toBe('contents of the file');
|
}, input)).toBe('contents of the file');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should upload a folder', async ({ page, server, browserName, headless, browserVersion, isAndroid }) => {
|
it('should upload a folder', async ({ page, server, browserName, headless, browserMajorVersion, isAndroid }) => {
|
||||||
it.skip(isAndroid);
|
it.skip(isAndroid);
|
||||||
it.skip(os.platform() === 'darwin' && parseInt(os.release().split('.')[0], 10) <= 21, 'WebKit on macOS-12 is frozen');
|
it.skip(os.platform() === 'darwin' && parseInt(os.release().split('.')[0], 10) <= 21, 'WebKit on macOS-12 is frozen');
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ it('should upload a folder', async ({ page, server, browserName, headless, brows
|
||||||
await input.setInputFiles(dir);
|
await input.setInputFiles(dir);
|
||||||
expect(new Set(await page.evaluate(e => [...e.files].map(f => f.webkitRelativePath), input))).toEqual(new Set([
|
expect(new Set(await page.evaluate(e => [...e.files].map(f => f.webkitRelativePath), input))).toEqual(new Set([
|
||||||
// https://issues.chromium.org/issues/345393164
|
// https://issues.chromium.org/issues/345393164
|
||||||
...((browserName === 'chromium' && headless && !process.env.PLAYWRIGHT_CHROMIUM_USE_HEADLESS_NEW && chromiumVersionLessThan(browserVersion, '127.0.6533.0')) ? [] : ['file-upload-test/sub-dir/really.txt']),
|
...((browserName === 'chromium' && headless && !process.env.PLAYWRIGHT_CHROMIUM_USE_HEADLESS_NEW && browserMajorVersion < 127) ? [] : ['file-upload-test/sub-dir/really.txt']),
|
||||||
'file-upload-test/file1.txt',
|
'file-upload-test/file1.txt',
|
||||||
'file-upload-test/file2',
|
'file-upload-test/file2',
|
||||||
]));
|
]));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue