Merge branch 'main' into tar-download-3rd-party-lib

This commit is contained in:
Simon Knott 2024-12-19 16:47:37 +01:00
commit affff4d6ec
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
19 changed files with 171 additions and 40 deletions

View file

@ -129,7 +129,13 @@ You can use the `globalSetup` option in the [configuration file](./test-configur
Similarly, use `globalTeardown` to run something once after all the tests. Alternatively, let `globalSetup` return a function that will be used as a global teardown. You can pass data such as port number, authentication tokens, etc. from your global setup to your tests using environment variables.
:::note
Using `globalSetup` and `globalTeardown` will not produce traces or artifacts, and options like `headless` or `testIdAttribute` specified in the config file are not applied. If you want to produce traces and artifacts and respect config options, use [project dependencies](#option-1-project-dependencies).
Beware of `globalSetup` and `globalTeardown` caveats:
- These methods will not produce traces or artifacts unless explictly enabled, as described in [Capturing trace of failures during global setup](#capturing-trace-of-failures-during-global-setup).
- Options sush as `headless` or `testIdAttribute` specified in the config file are not applied,
- An uncaught exception thrown in `globalSetup` will prevent Playwright from running tests, and no test results will appear in reporters.
Consider using [project dependencies](#option-1-project-dependencies) to produce traces, artifacts, respect config options and get test results in reporters even in case of a setup failure.
:::
```js title="playwright.config.ts"

View file

@ -27,7 +27,7 @@
},
{
"name": "webkit",
"revision": "2119",
"revision": "2120",
"installByDefault": true,
"revisionOverrides": {
"debian11-x64": "2105",
@ -45,7 +45,7 @@
},
{
"name": "ffmpeg",
"revision": "1010",
"revision": "1011",
"installByDefault": true,
"revisionOverrides": {
"mac12": "1010",

View file

@ -32,6 +32,7 @@ import { debugLogger } from '../utils/debugLogger';
export type ClientType = 'controller' | 'launch-browser' | 'reuse-browser' | 'pre-launched-browser-or-android';
type Options = {
allowFSPaths: boolean,
socksProxyPattern: string | undefined,
browserName: string | null,
launchOptions: LaunchOptions,
@ -60,7 +61,7 @@ export class PlaywrightConnection {
this._ws = ws;
this._preLaunched = preLaunched;
this._options = options;
options.launchOptions = filterLaunchOptions(options.launchOptions);
options.launchOptions = filterLaunchOptions(options.launchOptions, options.allowFSPaths);
if (clientType === 'reuse-browser' || clientType === 'pre-launched-browser-or-android')
assert(preLaunched.playwright);
if (clientType === 'pre-launched-browser-or-android')
@ -284,7 +285,7 @@ function launchOptionsHash(options: LaunchOptions) {
return JSON.stringify(copy);
}
function filterLaunchOptions(options: LaunchOptions): LaunchOptions {
function filterLaunchOptions(options: LaunchOptions, allowFSPaths: boolean): LaunchOptions {
return {
channel: options.channel,
args: options.args,
@ -296,7 +297,8 @@ function filterLaunchOptions(options: LaunchOptions): LaunchOptions {
chromiumSandbox: options.chromiumSandbox,
firefoxUserPrefs: options.firefoxUserPrefs,
slowMo: options.slowMo,
executablePath: isUnderTest() ? options.executablePath : undefined,
executablePath: (isUnderTest() || allowFSPaths) ? options.executablePath : undefined,
downloadsPath: allowFSPaths ? options.downloadsPath : undefined,
};
}

View file

@ -102,7 +102,7 @@ export class PlaywrightServer {
return new PlaywrightConnection(
semaphore.acquire(),
clientType, ws,
{ socksProxyPattern: proxyValue, browserName, launchOptions },
{ socksProxyPattern: proxyValue, browserName, launchOptions, allowFSPaths: this._options.mode === 'extension' },
{
playwright: this._preLaunchedPlaywright,
browser: this._options.preLaunchedBrowser,

View file

@ -171,8 +171,10 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.${toPascal(options.browserName)}.LaunchAsync(${formatObject(options.launchOptions, ' ', 'BrowserTypeLaunchOptions')});
var context = await browser.NewContextAsync(${formatContextOptions(options.contextOptions, options.deviceName)});`);
if (options.contextOptions.recordHar)
formatter.add(` await context.RouteFromHARAsync(${quote(options.contextOptions.recordHar.path)});`);
if (options.contextOptions.recordHar) {
const url = options.contextOptions.recordHar.urlFilter;
formatter.add(` await context.RouteFromHARAsync(${quote(options.contextOptions.recordHar.path)}${url ? `, ${formatObject({ url }, ' ', 'BrowserContextRouteFromHAROptions')}` : ''});`);
}
formatter.newLine();
return formatter.format();
}
@ -198,8 +200,10 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
formatter.add(` [${this._mode === 'nunit' ? 'Test' : 'TestMethod'}]
public async Task MyTest()
{`);
if (options.contextOptions.recordHar)
formatter.add(` await context.RouteFromHARAsync(${quote(options.contextOptions.recordHar.path)});`);
if (options.contextOptions.recordHar) {
const url = options.contextOptions.recordHar.urlFilter;
formatter.add(` await Context.RouteFromHARAsync(${quote(options.contextOptions.recordHar.path)}${url ? `, ${formatObject({ url }, ' ', 'BrowserContextRouteFromHAROptions')}` : ''});`);
}
return formatter.format();
}

View file

@ -150,28 +150,38 @@ export class JavaLanguageGenerator implements LanguageGenerator {
import com.microsoft.playwright.Page;
import com.microsoft.playwright.options.*;
import org.junit.jupiter.api.*;
${options.contextOptions.recordHar ? `import java.nio.file.Paths;\n` : ''}import org.junit.jupiter.api.*;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.*;
@UsePlaywright
public class TestExample {
@Test
void test(Page page) {`);
if (options.contextOptions.recordHar) {
const url = options.contextOptions.recordHar.urlFilter;
const recordHarOptions = typeof url === 'string' ? `, new Page.RouteFromHAROptions()
.setUrl(${quote(url)})` : '';
formatter.add(` page.routeFromHAR(Paths.get(${quote(options.contextOptions.recordHar.path)})${recordHarOptions});`);
}
return formatter.format();
}
formatter.add(`
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import java.util.*;
${options.contextOptions.recordHar ? `import java.nio.file.Paths;\n` : ''}import java.util.*;
public class Example {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.${options.browserName}().launch(${formatLaunchOptions(options.launchOptions)});
BrowserContext context = browser.newContext(${formatContextOptions(options.contextOptions, options.deviceName)});`);
if (options.contextOptions.recordHar)
formatter.add(` context.routeFromHAR(${quote(options.contextOptions.recordHar.path)});`);
if (options.contextOptions.recordHar) {
const url = options.contextOptions.recordHar.urlFilter;
const recordHarOptions = typeof url === 'string' ? `, new BrowserContext.RouteFromHAROptions()
.setUrl(${quote(url)})` : '';
formatter.add(` context.routeFromHAR(Paths.get(${quote(options.contextOptions.recordHar.path)})${recordHarOptions});`);
}
return formatter.format();
}

View file

@ -147,8 +147,10 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator {
import { test, expect${options.deviceName ? ', devices' : ''} } from '@playwright/test';
${useText ? '\ntest.use(' + useText + ');\n' : ''}
test('test', async ({ page }) => {`);
if (options.contextOptions.recordHar)
formatter.add(` await page.routeFromHAR(${quote(options.contextOptions.recordHar.path)});`);
if (options.contextOptions.recordHar) {
const url = options.contextOptions.recordHar.urlFilter;
formatter.add(` await page.routeFromHAR(${quote(options.contextOptions.recordHar.path)}${url ? `, ${formatOptions({ url }, false)}` : ''});`);
}
return formatter.format();
}

View file

@ -137,6 +137,7 @@ export class PythonLanguageGenerator implements LanguageGenerator {
generateHeader(options: LanguageGeneratorOptions): string {
const formatter = new PythonFormatter();
const recordHar = options.contextOptions.recordHar;
if (this._isPyTest) {
const contextOptions = formatContextOptions(options.contextOptions, options.deviceName, true /* asDict */);
const fixture = contextOptions ? `
@ -146,13 +147,13 @@ def browser_context_args(browser_context_args, playwright) {
return {${contextOptions}}
}
` : '';
formatter.add(`${options.deviceName ? 'import pytest\n' : ''}import re
formatter.add(`${options.deviceName || contextOptions ? 'import pytest\n' : ''}import re
from playwright.sync_api import Page, expect
${fixture}
def test_example(page: Page) -> None {`);
if (options.contextOptions.recordHar)
formatter.add(` page.route_from_har(${quote(options.contextOptions.recordHar.path)})`);
if (recordHar)
formatter.add(` page.route_from_har(${quote(recordHar.path)}${typeof recordHar.urlFilter === 'string' ? `, url=${quote(recordHar.urlFilter)}` : ''})`);
} else if (this._isAsync) {
formatter.add(`
import asyncio
@ -163,8 +164,8 @@ from playwright.async_api import Playwright, async_playwright, expect
async def run(playwright: Playwright) -> None {
browser = await playwright.${options.browserName}.launch(${formatOptions(options.launchOptions, false)})
context = await browser.new_context(${formatContextOptions(options.contextOptions, options.deviceName)})`);
if (options.contextOptions.recordHar)
formatter.add(` await page.route_from_har(${quote(options.contextOptions.recordHar.path)})`);
if (recordHar)
formatter.add(` await context.route_from_har(${quote(recordHar.path)}${typeof recordHar.urlFilter === 'string' ? `, url=${quote(recordHar.urlFilter)}` : ''})`);
} else {
formatter.add(`
import re
@ -174,8 +175,8 @@ from playwright.sync_api import Playwright, sync_playwright, expect
def run(playwright: Playwright) -> None {
browser = playwright.${options.browserName}.launch(${formatOptions(options.launchOptions, false)})
context = browser.new_context(${formatContextOptions(options.contextOptions, options.deviceName)})`);
if (options.contextOptions.recordHar)
formatter.add(` context.route_from_har(${quote(options.contextOptions.recordHar.path)})`);
if (recordHar)
formatter.add(` context.route_from_har(${quote(recordHar.path)}${typeof recordHar.urlFilter === 'string' ? `, url=${quote(recordHar.urlFilter)}` : ''})`);
}
return formatter.format();
}

View file

@ -37,13 +37,9 @@ const PACKAGE_PATH = path.join(__dirname, '..', '..', '..');
const BIN_PATH = path.join(__dirname, '..', '..', '..', 'bin');
const PLAYWRIGHT_CDN_MIRRORS = [
'https://playwright.azureedge.net/dbazure/download/playwright', // ESRP CDN
'https://cdn.playwright.dev/dbazure/download/playwright', // ESRP CDN
'https://playwright.download.prss.microsoft.com/dbazure/download/playwright', // Directly hit ESRP CDN
// Old endpoints which hit the Storage Bucket directly:
'https://playwright.azureedge.net',
'https://playwright-akamai.azureedge.net', // Actually Edgio which will be retired Q4 2025.
'https://playwright-verizon.azureedge.net', // Actually Edgio which will be retired Q4 2025.
'https://cdn.playwright.dev', // Hit the Storage Bucket directly
];
if (process.env.PW_TEST_CDN_THAT_SHOULD_WORK) {

View file

@ -19,11 +19,9 @@ import net from 'net';
import type { AddressInfo } from 'net';
const CDNS = [
'https://playwright.azureedge.net/dbazure/download/playwright', // ESRP
'https://cdn.playwright.dev/dbazure/download/playwright', // ESRP
'https://playwright.download.prss.microsoft.com/dbazure/download/playwright', // ESRP Fallback
'https://playwright.azureedge.net',
'https://playwright-akamai.azureedge.net',
'https://playwright-verizon.azureedge.net',
'https://cdn.playwright.dev',
];
const DL_STAT_BLOCK = /^.*from url: (.*)$\n^.*to location: (.*)$\n^.*response status code: (.*)$\n^.*total bytes: (\d+)$\n^.*download complete, size: (\d+)$\n^.*SUCCESS downloading (?:and extracting )?(\w+) .*$/gm;

View file

@ -179,6 +179,20 @@ test('should work with --save-har', async ({ runCLI }, testInfo) => {
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 BrowserContextRouteFromHAROptions
{
Url = "**/*.js",
});`;
const cli = runCLI(['--target=csharp', `--save-har=${harFileName}`, '--save-har-glob=**/*.js'], {
autoExitWhen: expectedResult,
});
await cli.waitForCleanExit();
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
expect(json.log.creator.name).toBe('Playwright');
});
for (const testFramework of ['nunit', 'mstest'] as const) {
test(`should not print context options method override in ${testFramework} if no options were passed`, async ({ runCLI }) => {
const cli = runCLI([`--target=csharp-${testFramework}`, emptyHTML]);
@ -201,7 +215,7 @@ for (const testFramework of ['nunit', 'mstest'] as const) {
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 expectedResult = `await Context.RouteFromHARAsync(${JSON.stringify(harFileName)});`;
const cli = runCLI([`--target=csharp-${testFramework}`, `--save-har=${harFileName}`], {
autoExitWhen: expectedResult,
});
@ -209,6 +223,20 @@ for (const testFramework of ['nunit', 'mstest'] as const) {
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 BrowserContextRouteFromHAROptions
{
Url = "**/*.js",
});`;
const cli = runCLI([`--target=csharp-${testFramework}`, `--save-har=${harFileName}`, '--save-har-glob=**/*.js'], {
autoExitWhen: expectedResult,
});
await cli.waitForCleanExit();
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 }) => {

View file

@ -89,10 +89,24 @@ test('should print load/save storage_state', async ({ runCLI, browserName }, tes
await cli.waitFor(expectedResult2);
});
test('should work with --save-har', async ({ runCLI }, testInfo) => {
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(${JSON.stringify(harFileName)});`;
const cli = runCLI(['--target=java', `--save-har=${harFileName}`], {
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'], {
autoExitWhen: expectedResult,
});
await cli.waitForCleanExit();
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'], {
autoExitWhen: expectedResult,
});

View file

@ -69,3 +69,25 @@ def test_example(page: Page) -> None:
page.goto("${emptyHTML}")
`);
});
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}`], {
autoExitWhen: expectedResult,
});
await cli.waitForCleanExit();
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'], {
autoExitWhen: expectedResult,
});
await cli.waitForCleanExit();
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
expect(json.log.creator.name).toBe('Playwright');
});

View file

@ -146,7 +146,7 @@ asyncio.run(main())
test('should work with --save-har', async ({ runCLI }, testInfo) => {
const harFileName = testInfo.outputPath('har.har');
const expectedResult = `await page.route_from_har(${JSON.stringify(harFileName)})`;
const expectedResult = `await context.route_from_har(${JSON.stringify(harFileName)})`;
const cli = runCLI(['--target=python-async', `--save-har=${harFileName}`], {
autoExitWhen: expectedResult,
});
@ -154,3 +154,14 @@ test('should work with --save-har', async ({ runCLI }, testInfo) => {
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'], {
autoExitWhen: expectedResult,
});
await cli.waitForCleanExit();
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
expect(json.log.creator.name).toBe('Playwright');
});

View file

@ -129,3 +129,25 @@ with sync_playwright() as 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}`], {
autoExitWhen: expectedResult,
});
await cli.waitForCleanExit();
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'], {
autoExitWhen: expectedResult,
});
await cli.waitForCleanExit();
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
expect(json.log.creator.name).toBe('Playwright');
});

View file

@ -108,3 +108,18 @@ test('should generate routeFromHAR with --save-har', async ({ runCLI }, testInfo
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'], {
autoExitWhen: expectedResult,
});
await cli.waitForCleanExit();
const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8'));
expect(json.log.creator.name).toBe('Playwright');
});

View file

@ -362,7 +362,7 @@ function writeFile(filePath, content) {
fs.writeFileSync(filePath, content, 'utf8');
}
writeFile(path.join(__dirname, '..', 'packages', 'protocol', 'src', 'channels.ts'), channels_ts.join('\n'));
writeFile(path.join(__dirname, '..', 'packages', 'protocol', 'src', 'channels.d.ts'), channels_ts.join('\n'));
writeFile(path.join(__dirname, '..', 'packages', 'playwright-core', 'src', 'protocol', 'debug.ts'), debug_ts.join('\n'));
writeFile(path.join(__dirname, '..', 'packages', 'playwright-core', 'src', 'protocol', 'validator.ts'), validator_ts.join('\n'));
process.exit(hasChanges ? 1 : 0);