test: migrate to upstream fixtures (#9835)
This commit is contained in:
Родитель
9af5aaabbb
Коммит
2e4722d460
|
@ -997,17 +997,17 @@ export type BrowserContextNewPageResult = {
|
|||
page: PageChannel,
|
||||
};
|
||||
export type BrowserContextSetDefaultNavigationTimeoutNoReplyParams = {
|
||||
timeout: number,
|
||||
timeout?: number,
|
||||
};
|
||||
export type BrowserContextSetDefaultNavigationTimeoutNoReplyOptions = {
|
||||
|
||||
timeout?: number,
|
||||
};
|
||||
export type BrowserContextSetDefaultNavigationTimeoutNoReplyResult = void;
|
||||
export type BrowserContextSetDefaultTimeoutNoReplyParams = {
|
||||
timeout: number,
|
||||
timeout?: number,
|
||||
};
|
||||
export type BrowserContextSetDefaultTimeoutNoReplyOptions = {
|
||||
|
||||
timeout?: number,
|
||||
};
|
||||
export type BrowserContextSetDefaultTimeoutNoReplyResult = void;
|
||||
export type BrowserContextSetExtraHTTPHeadersParams = {
|
||||
|
@ -1253,17 +1253,17 @@ export type PageWorkerEvent = {
|
|||
worker: WorkerChannel,
|
||||
};
|
||||
export type PageSetDefaultNavigationTimeoutNoReplyParams = {
|
||||
timeout: number,
|
||||
timeout?: number,
|
||||
};
|
||||
export type PageSetDefaultNavigationTimeoutNoReplyOptions = {
|
||||
|
||||
timeout?: number,
|
||||
};
|
||||
export type PageSetDefaultNavigationTimeoutNoReplyResult = void;
|
||||
export type PageSetDefaultTimeoutNoReplyParams = {
|
||||
timeout: number,
|
||||
timeout?: number,
|
||||
};
|
||||
export type PageSetDefaultTimeoutNoReplyOptions = {
|
||||
|
||||
timeout?: number,
|
||||
};
|
||||
export type PageSetDefaultTimeoutNoReplyResult = void;
|
||||
export type PageSetFileChooserInterceptedNoReplyParams = {
|
||||
|
|
|
@ -733,11 +733,11 @@ BrowserContext:
|
|||
|
||||
setDefaultNavigationTimeoutNoReply:
|
||||
parameters:
|
||||
timeout: number
|
||||
timeout: number?
|
||||
|
||||
setDefaultTimeoutNoReply:
|
||||
parameters:
|
||||
timeout: number
|
||||
timeout: number?
|
||||
|
||||
setExtraHTTPHeaders:
|
||||
parameters:
|
||||
|
@ -897,11 +897,11 @@ Page:
|
|||
|
||||
setDefaultNavigationTimeoutNoReply:
|
||||
parameters:
|
||||
timeout: number
|
||||
timeout: number?
|
||||
|
||||
setDefaultTimeoutNoReply:
|
||||
parameters:
|
||||
timeout: number
|
||||
timeout: number?
|
||||
|
||||
setFileChooserInterceptedNoReply:
|
||||
parameters:
|
||||
|
|
|
@ -455,10 +455,10 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||
});
|
||||
scheme.BrowserContextNewPageParams = tOptional(tObject({}));
|
||||
scheme.BrowserContextSetDefaultNavigationTimeoutNoReplyParams = tObject({
|
||||
timeout: tNumber,
|
||||
timeout: tOptional(tNumber),
|
||||
});
|
||||
scheme.BrowserContextSetDefaultTimeoutNoReplyParams = tObject({
|
||||
timeout: tNumber,
|
||||
timeout: tOptional(tNumber),
|
||||
});
|
||||
scheme.BrowserContextSetExtraHTTPHeadersParams = tObject({
|
||||
headers: tArray(tType('NameValue')),
|
||||
|
@ -511,10 +511,10 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||
scheme.BrowserContextTracingStopParams = tOptional(tObject({}));
|
||||
scheme.BrowserContextHarExportParams = tOptional(tObject({}));
|
||||
scheme.PageSetDefaultNavigationTimeoutNoReplyParams = tObject({
|
||||
timeout: tNumber,
|
||||
timeout: tOptional(tNumber),
|
||||
});
|
||||
scheme.PageSetDefaultTimeoutNoReplyParams = tObject({
|
||||
timeout: tNumber,
|
||||
timeout: tOptional(tNumber),
|
||||
});
|
||||
scheme.PageSetFileChooserInterceptedNoReplyParams = tObject({
|
||||
intercepted: tBoolean,
|
||||
|
|
|
@ -204,11 +204,11 @@ export abstract class BrowserContext extends SdkObject {
|
|||
await this._doClearPermissions();
|
||||
}
|
||||
|
||||
setDefaultNavigationTimeout(timeout: number) {
|
||||
setDefaultNavigationTimeout(timeout: number | undefined) {
|
||||
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
|
||||
}
|
||||
|
||||
setDefaultTimeout(timeout: number) {
|
||||
setDefaultTimeout(timeout: number | undefined) {
|
||||
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||
}
|
||||
|
||||
|
|
|
@ -260,11 +260,11 @@ export class Page extends SdkObject {
|
|||
return this._frameManager.frames();
|
||||
}
|
||||
|
||||
setDefaultNavigationTimeout(timeout: number) {
|
||||
setDefaultNavigationTimeout(timeout: number | undefined) {
|
||||
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
|
||||
}
|
||||
|
||||
setDefaultTimeout(timeout: number) {
|
||||
setDefaultTimeout(timeout: number | undefined) {
|
||||
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||
}
|
||||
|
||||
|
|
|
@ -218,7 +218,7 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
|||
|
||||
const zipArtifact = skipCompress ? null : await this._exportZip(entries, state).catch(() => null);
|
||||
return { artifact: zipArtifact, entries };
|
||||
});
|
||||
}) || { artifact: null, entries: [] };
|
||||
}
|
||||
|
||||
private async _exportZip(entries: NameValue[], state: RecordingState): Promise<Artifact | null> {
|
||||
|
@ -360,11 +360,13 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
|||
});
|
||||
}
|
||||
|
||||
private async _appendTraceOperation<T>(cb: () => Promise<T>): Promise<T> {
|
||||
private async _appendTraceOperation<T>(cb: () => Promise<T>): Promise<T | undefined> {
|
||||
// This method serializes all writes to the trace.
|
||||
let error: Error | undefined;
|
||||
let result: T | undefined;
|
||||
this._writeChain = this._writeChain.then(async () => {
|
||||
if (!this._context._browser.isConnected())
|
||||
return;
|
||||
try {
|
||||
result = await cb();
|
||||
} catch (e) {
|
||||
|
@ -374,7 +376,7 @@ export class Tracing implements InstrumentationListener, SnapshotterDelegate, Ha
|
|||
await this._writeChain;
|
||||
if (error)
|
||||
throw error;
|
||||
return result!;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,27 +22,27 @@ const TIMEOUT = debugMode() ? 0 : DEFAULT_TIMEOUT;
|
|||
|
||||
export class TimeoutSettings {
|
||||
private _parent: TimeoutSettings | undefined;
|
||||
private _defaultTimeout: number | null = null;
|
||||
private _defaultNavigationTimeout: number | null = null;
|
||||
private _defaultTimeout: number | undefined;
|
||||
private _defaultNavigationTimeout: number | undefined;
|
||||
|
||||
constructor(parent?: TimeoutSettings) {
|
||||
this._parent = parent;
|
||||
}
|
||||
|
||||
setDefaultTimeout(timeout: number) {
|
||||
setDefaultTimeout(timeout: number | undefined) {
|
||||
this._defaultTimeout = timeout;
|
||||
}
|
||||
|
||||
setDefaultNavigationTimeout(timeout: number) {
|
||||
setDefaultNavigationTimeout(timeout: number | undefined) {
|
||||
this._defaultNavigationTimeout = timeout;
|
||||
}
|
||||
|
||||
navigationTimeout(options: { timeout?: number }): number {
|
||||
if (typeof options.timeout === 'number')
|
||||
return options.timeout;
|
||||
if (this._defaultNavigationTimeout !== null)
|
||||
if (this._defaultNavigationTimeout !== undefined)
|
||||
return this._defaultNavigationTimeout;
|
||||
if (this._defaultTimeout !== null)
|
||||
if (this._defaultTimeout !== undefined)
|
||||
return this._defaultTimeout;
|
||||
if (this._parent)
|
||||
return this._parent.navigationTimeout(options);
|
||||
|
@ -52,7 +52,7 @@ export class TimeoutSettings {
|
|||
timeout(options: { timeout?: number }): number {
|
||||
if (typeof options.timeout === 'number')
|
||||
return options.timeout;
|
||||
if (this._defaultTimeout !== null)
|
||||
if (this._defaultTimeout !== undefined)
|
||||
return this._defaultTimeout;
|
||||
if (this._parent)
|
||||
return this._parent.timeout(options);
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { Command, Option } from 'commander';
|
||||
import { Command } from 'commander';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import type { Config } from './types';
|
||||
|
@ -49,7 +49,6 @@ export function addTestCommand(program: Command) {
|
|||
command.option('--browser <browser>', `Browser to use for tests, one of "all", "chromium", "firefox" or "webkit" (default: "chromium")`);
|
||||
command.option('--headed', `Run tests in headed browsers (default: headless)`);
|
||||
command.option('--debug', `Run tests with Playwright Inspector. Shortcut for "PWDEBUG=1" environment variable and "--timeout=0 --maxFailures=1 --headed --workers=1" options`);
|
||||
command.addOption(new Option('--reuse-context').hideHelp());
|
||||
command.option('-c, --config <file>', `Configuration file, or a test directory with optional "${tsConfig}"/"${jsConfig}"`);
|
||||
command.option('--forbid-only', `Fail if test.only is called (default: false)`);
|
||||
command.option('-g, --grep <grep>', `Only run tests matching this regular expression (default: ".*")`);
|
||||
|
@ -114,17 +113,15 @@ async function createLoader(opts: { [key: string]: any }): Promise<Loader> {
|
|||
}
|
||||
|
||||
const overrides = overridesFromOptions(opts);
|
||||
if (opts.headed || opts.debug || opts.reuseContext)
|
||||
if (opts.headed || opts.debug)
|
||||
overrides.use = { headless: false };
|
||||
if (opts.debug || opts.reuseContext) {
|
||||
if (opts.debug) {
|
||||
overrides.maxFailures = 1;
|
||||
overrides.timeout = 0;
|
||||
overrides.workers = 1;
|
||||
}
|
||||
if (opts.debug)
|
||||
process.env.PWDEBUG = '1';
|
||||
if (opts.reuseContext)
|
||||
process.env.PWTEST_REUSE_CONTEXT = '1';
|
||||
|
||||
const loader = new Loader(defaultConfig, overrides);
|
||||
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import type { LaunchOptions, BrowserContextOptions, Page, BrowserContext, BrowserType } from 'playwright-core';
|
||||
import type { LaunchOptions, BrowserContextOptions, Page, BrowserContext, BrowserType, Video } from 'playwright-core';
|
||||
import type { TestType, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, TestInfo } from '../types/test';
|
||||
import { rootTestType } from './testType';
|
||||
import { assert, createGuid, removeFolders } from 'playwright-core/lib/utils/utils';
|
||||
import { createGuid, removeFolders } from 'playwright-core/lib/utils/utils';
|
||||
import { GridClient } from 'playwright-core/lib/grid/gridClient';
|
||||
import { Browser } from 'playwright-core';
|
||||
export { expect } from './expect';
|
||||
|
@ -28,97 +28,15 @@ export const _baseTest: TestType<{}, {}> = rootTestType.test;
|
|||
type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & {
|
||||
_combinedContextOptions: BrowserContextOptions,
|
||||
_setupContextOptionsAndArtifacts: void;
|
||||
_contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>;
|
||||
};
|
||||
type WorkerAndFileFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
|
||||
_browserType: BrowserType;
|
||||
_browserOptions: LaunchOptions;
|
||||
_artifactsDir: () => string,
|
||||
_reuseBrowserContext: ReuseBrowserContextStorage,
|
||||
_artifactsDir: () => string;
|
||||
_snapshotSuffix: string;
|
||||
};
|
||||
|
||||
export class ReuseBrowserContextStorage {
|
||||
private _browserContext?: BrowserContext;
|
||||
private _uniqueOrigins = new Set<string>();
|
||||
private _options?: BrowserContextOptions;
|
||||
private _pauseNavigationEventCollection = false;
|
||||
|
||||
isEnabled(): boolean {
|
||||
return !!process.env.PWTEST_REUSE_CONTEXT;
|
||||
}
|
||||
|
||||
async obtainContext(browser: Browser, newContextOptions: BrowserContextOptions): Promise<BrowserContext> {
|
||||
if (!this._browserContext)
|
||||
return await this._createNewContext(browser);
|
||||
return await this._refurbishExistingContext(newContextOptions);
|
||||
}
|
||||
|
||||
private async _createNewContext(browser: Browser): Promise<BrowserContext> {
|
||||
this._browserContext = await browser.newContext();
|
||||
this._options = (this._browserContext as any)._options;
|
||||
this._browserContext.on('page', page => {
|
||||
page.on('framenavigated', frame => {
|
||||
if (this._pauseNavigationEventCollection)
|
||||
return;
|
||||
const origin = new URL(frame.url()).origin;
|
||||
if (origin !== 'null') // 'chrome-error://chromewebdata/'
|
||||
this._uniqueOrigins.add(origin);
|
||||
});
|
||||
page.on('crash', () => {
|
||||
this._browserContext?.close().then(() => {});
|
||||
this._browserContext = undefined;
|
||||
});
|
||||
});
|
||||
return this._browserContext;
|
||||
}
|
||||
|
||||
async _refurbishExistingContext(newContextOptions: BrowserContextOptions): Promise<BrowserContext> {
|
||||
assert(this._browserContext);
|
||||
const page = this._browserContext.pages().length > 0 ? this._browserContext.pages()[0] : await this._browserContext.newPage();
|
||||
this._pauseNavigationEventCollection = true;
|
||||
try {
|
||||
const initialOrigin = new URL(page.url()).origin;
|
||||
await page.route('**/*', route => route.fulfill({ body: `<html></html>`, contentType: 'text/html' }));
|
||||
while (this._uniqueOrigins.size > 0) {
|
||||
const nextOrigin = this._uniqueOrigins.has(initialOrigin) ? initialOrigin : this._uniqueOrigins.values().next().value;
|
||||
this._uniqueOrigins.delete(nextOrigin);
|
||||
await page.goto(nextOrigin);
|
||||
await page.evaluate(() => window.localStorage.clear());
|
||||
await page.evaluate(() => window.sessionStorage.clear());
|
||||
}
|
||||
await page.unroute('**/*');
|
||||
await page.goto('about:blank');
|
||||
await Promise.all(this._browserContext.pages().slice(1).map(page => page.close()));
|
||||
await this._browserContext.clearCookies();
|
||||
await this._applyNewContextOptions(page, newContextOptions);
|
||||
} finally {
|
||||
this._pauseNavigationEventCollection = false;
|
||||
}
|
||||
return this._browserContext;
|
||||
}
|
||||
|
||||
private async _applyNewContextOptions(page: Page, newOptions: BrowserContextOptions) {
|
||||
assert(this._options);
|
||||
const currentViewport = page.viewportSize();
|
||||
const newViewport = newOptions.viewport === undefined ? { width: 1280, height: 720 } : newOptions.viewport;
|
||||
if (
|
||||
(
|
||||
currentViewport?.width !== newViewport?.width ||
|
||||
currentViewport?.height !== newViewport?.height
|
||||
) &&
|
||||
(newViewport?.height && newViewport?.width)
|
||||
)
|
||||
await page.setViewportSize(newViewport);
|
||||
this._options = newOptions;
|
||||
}
|
||||
|
||||
async obtainPage(): Promise<Page> {
|
||||
assert(this._browserContext);
|
||||
if (this._browserContext.pages().length === 0)
|
||||
return await this._browserContext.newPage();
|
||||
return this._browserContext.pages()[0];
|
||||
}
|
||||
}
|
||||
|
||||
export const test = _baseTest.extend<TestFixtures, WorkerAndFileFixtures>({
|
||||
defaultBrowserType: [ 'chromium', { scope: 'worker' } ],
|
||||
browserName: [ ({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: 'worker' } ],
|
||||
|
@ -251,8 +169,10 @@ export const test = _baseTest.extend<TestFixtures, WorkerAndFileFixtures>({
|
|||
});
|
||||
},
|
||||
|
||||
_setupContextOptionsAndArtifacts: [async ({ _browserType, _combinedContextOptions, _artifactsDir, trace, screenshot, actionTimeout, navigationTimeout }, use, testInfo) => {
|
||||
testInfo.snapshotSuffix = process.platform;
|
||||
_snapshotSuffix: [process.platform, { scope: 'worker' }],
|
||||
|
||||
_setupContextOptionsAndArtifacts: [async ({ _snapshotSuffix, _browserType, _combinedContextOptions, _artifactsDir, trace, screenshot, actionTimeout, navigationTimeout }, use, testInfo) => {
|
||||
testInfo.snapshotSuffix = _snapshotSuffix;
|
||||
if (process.env.PWDEBUG)
|
||||
testInfo.setTimeout(0);
|
||||
|
||||
|
@ -381,39 +301,55 @@ export const test = _baseTest.extend<TestFixtures, WorkerAndFileFixtures>({
|
|||
}));
|
||||
}, { auto: true }],
|
||||
|
||||
_reuseBrowserContext: [new ReuseBrowserContextStorage(), { scope: 'worker' }],
|
||||
|
||||
context: async ({ browser, video, _artifactsDir, _reuseBrowserContext, _combinedContextOptions }, use, testInfo) => {
|
||||
const hook = hookType(testInfo);
|
||||
if (hook)
|
||||
throw new Error(`"context" and "page" fixtures are not supported in ${hook}. Use browser.newContext() instead.`);
|
||||
if (_reuseBrowserContext.isEnabled()) {
|
||||
const context = await _reuseBrowserContext.obtainContext(browser, _combinedContextOptions);
|
||||
await use(context);
|
||||
return;
|
||||
}
|
||||
|
||||
_contextFactory: async ({ browser, video, _artifactsDir }, use, testInfo) => {
|
||||
let videoMode = typeof video === 'string' ? video : video.mode;
|
||||
if (videoMode === 'retry-with-video')
|
||||
videoMode = 'on-first-retry';
|
||||
|
||||
const captureVideo = (videoMode === 'on' || videoMode === 'retain-on-failure' || (videoMode === 'on-first-retry' && testInfo.retry === 1));
|
||||
const videoOptions: BrowserContextOptions = captureVideo ? {
|
||||
recordVideo: {
|
||||
dir: _artifactsDir(),
|
||||
size: typeof video === 'string' ? undefined : video.size,
|
||||
}
|
||||
} : {};
|
||||
const context = await browser.newContext(videoOptions);
|
||||
const contexts = new Map<BrowserContext, { pages: Page[] }>();
|
||||
|
||||
const allPages: Page[] = [];
|
||||
context.on('page', page => allPages.push(page));
|
||||
|
||||
await use(context);
|
||||
await use(async options => {
|
||||
const hook = hookType(testInfo);
|
||||
if (hook)
|
||||
throw new Error(`"context" and "page" fixtures are not supported in ${hook}. Use browser.newContext() instead.`);
|
||||
const videoOptions: BrowserContextOptions = captureVideo ? {
|
||||
recordVideo: {
|
||||
dir: _artifactsDir(),
|
||||
size: typeof video === 'string' ? undefined : video.size,
|
||||
}
|
||||
} : {};
|
||||
const context = await browser.newContext({ ...videoOptions, ...options });
|
||||
const contextData: { pages: Page[] } = { pages: [] };
|
||||
contexts.set(context, contextData);
|
||||
context.on('page', page => contextData.pages.push(page));
|
||||
return context;
|
||||
});
|
||||
|
||||
const prependToError = testInfo.status === 'timedOut' ?
|
||||
formatPendingCalls((context as any)._connection.pendingProtocolCalls()) : '';
|
||||
await context.close();
|
||||
formatPendingCalls((browser as any)._connection.pendingProtocolCalls()) : '';
|
||||
|
||||
await Promise.all([...contexts.keys()].map(async context => {
|
||||
await context.close();
|
||||
|
||||
const testFailed = testInfo.status !== testInfo.expectedStatus;
|
||||
const preserveVideo = captureVideo && (videoMode === 'on' || (testFailed && videoMode === 'retain-on-failure') || (videoMode === 'on-first-retry' && testInfo.retry === 1));
|
||||
if (preserveVideo) {
|
||||
const { pages } = contexts.get(context)!;
|
||||
const videos = pages.map(p => p.video()).filter(Boolean) as Video[];
|
||||
await Promise.all(videos.map(async v => {
|
||||
try {
|
||||
const videoPath = await v.path();
|
||||
const savedPath = testInfo.outputPath(path.basename(videoPath));
|
||||
await v.saveAs(savedPath);
|
||||
testInfo.attachments.push({ name: 'video', path: savedPath, contentType: 'video/webm' });
|
||||
} catch (e) {
|
||||
// Silent catch empty videos.
|
||||
}
|
||||
}));
|
||||
}
|
||||
}));
|
||||
|
||||
if (prependToError) {
|
||||
if (!testInfo.error) {
|
||||
testInfo.error = { value: prependToError };
|
||||
|
@ -423,31 +359,13 @@ export const test = _baseTest.extend<TestFixtures, WorkerAndFileFixtures>({
|
|||
testInfo.error.stack = prependToError + testInfo.error.stack;
|
||||
}
|
||||
}
|
||||
|
||||
const testFailed = testInfo.status !== testInfo.expectedStatus;
|
||||
const preserveVideo = captureVideo && (videoMode === 'on' || (testFailed && videoMode === 'retain-on-failure') || (videoMode === 'on-first-retry' && testInfo.retry === 1));
|
||||
if (preserveVideo) {
|
||||
await Promise.all(allPages.map(async page => {
|
||||
const v = page.video();
|
||||
if (!v)
|
||||
return;
|
||||
try {
|
||||
const videoPath = await v.path();
|
||||
const savedPath = testInfo.outputPath(path.basename(videoPath));
|
||||
await v.saveAs(savedPath);
|
||||
testInfo.attachments.push({ name: 'video', path: savedPath, contentType: 'video/webm' });
|
||||
} catch (e) {
|
||||
// Silent catch empty videos.
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
page: async ({ context, _reuseBrowserContext }, use) => {
|
||||
if (_reuseBrowserContext.isEnabled()) {
|
||||
await use(await _reuseBrowserContext.obtainPage());
|
||||
return;
|
||||
}
|
||||
context: async ({ _contextFactory }, use) => {
|
||||
await use(await _contextFactory());
|
||||
},
|
||||
|
||||
page: async ({ context }, use) => {
|
||||
await use(await context.newPage());
|
||||
},
|
||||
|
||||
|
|
|
@ -14,28 +14,27 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { baseTest } from '../config/baseTest';
|
||||
import { PageTestFixtures, PageWorkerFixtures } from '../page/pageTestApi';
|
||||
import type { AndroidDevice, BrowserContext } from 'playwright-core';
|
||||
import type { Fixtures, PlaywrightWorkerOptions } from '@playwright/test';
|
||||
import { PageTestFixtures } from '../page/pageTest';
|
||||
import { TestModeWorkerFixtures } from '../config/testModeFixtures';
|
||||
import { browserTest } from '../config/browserTest';
|
||||
export { expect } from '@playwright/test';
|
||||
|
||||
type AndroidWorkerFixtures = {
|
||||
type AndroidWorkerFixtures = PageWorkerFixtures & {
|
||||
androidDevice: AndroidDevice;
|
||||
androidContext: BrowserContext;
|
||||
};
|
||||
|
||||
export const androidFixtures: Fixtures<PageTestFixtures, AndroidWorkerFixtures & { androidContext: BrowserContext }, {}, PlaywrightWorkerOptions & TestModeWorkerFixtures> = {
|
||||
androidDevice: [ async ({ playwright }, run) => {
|
||||
export const androidTest = baseTest.extend<PageTestFixtures, AndroidWorkerFixtures>({
|
||||
androidDevice: [async ({ playwright }, run) => {
|
||||
const device = (await playwright._android.devices())[0];
|
||||
await device.shell('am force-stop org.chromium.webview_shell');
|
||||
await device.shell('am force-stop com.android.chrome');
|
||||
device.setDefaultTimeout(90000);
|
||||
await run(device);
|
||||
await device.close();
|
||||
}, { scope: 'worker' } ],
|
||||
}, { scope: 'worker' }],
|
||||
|
||||
browserVersion: async ({ androidDevice }, run) => {
|
||||
browserVersion: [async ({ androidDevice }, run) => {
|
||||
const browserVersion = (await androidDevice.shell('dumpsys package com.android.chrome'))
|
||||
.toString('utf8')
|
||||
.split('\n')
|
||||
|
@ -43,21 +42,21 @@ export const androidFixtures: Fixtures<PageTestFixtures, AndroidWorkerFixtures &
|
|||
.trim()
|
||||
.split('=')[1];
|
||||
await run(browserVersion);
|
||||
},
|
||||
}, { scope: 'worker' }],
|
||||
|
||||
browserMajorVersion: async ({ browserVersion }, run) => {
|
||||
browserMajorVersion: [async ({ browserVersion }, run) => {
|
||||
await run(Number(browserVersion.split('.')[0]));
|
||||
},
|
||||
}, { scope: 'worker' }],
|
||||
|
||||
isAndroid: true,
|
||||
isElectron: false,
|
||||
isAndroid: [true, { scope: 'worker' }],
|
||||
isElectron: [false, { scope: 'worker' }],
|
||||
|
||||
androidContext: [ async ({ androidDevice }, run) => {
|
||||
androidContext: [async ({ androidDevice }, run) => {
|
||||
const context = await androidDevice.launchBrowser();
|
||||
const [ page ] = context.pages();
|
||||
const [page] = context.pages();
|
||||
await page.goto('data:text/html,Default page');
|
||||
await run(context);
|
||||
}, { scope: 'worker' } ],
|
||||
}, { scope: 'worker' }],
|
||||
|
||||
page: async ({ androidContext }, run) => {
|
||||
// Retain default page, otherwise Clank will re-create it.
|
||||
|
@ -66,6 +65,4 @@ export const androidFixtures: Fixtures<PageTestFixtures, AndroidWorkerFixtures &
|
|||
const page = await androidContext.newPage();
|
||||
await run(page);
|
||||
},
|
||||
};
|
||||
|
||||
export const androidTest = browserTest.extend<PageTestFixtures, AndroidWorkerFixtures>(androidFixtures as any);
|
||||
});
|
||||
|
|
|
@ -134,10 +134,7 @@ it('close() should abort waitForEvent', async ({ browser }) => {
|
|||
|
||||
it('close() should be callable twice', async ({ browser }) => {
|
||||
const context = await browser.newContext();
|
||||
await Promise.all([
|
||||
context.close(),
|
||||
context.close(),
|
||||
]);
|
||||
await context.close();
|
||||
await context.close();
|
||||
});
|
||||
|
||||
|
|
|
@ -42,9 +42,8 @@ it.describe('device', () => {
|
|||
await context.close();
|
||||
});
|
||||
|
||||
it('should scroll to click', async ({ browser, server, contextOptions }) => {
|
||||
it('should scroll to click', async ({ browser, server }) => {
|
||||
const context = await browser.newContext({
|
||||
...contextOptions,
|
||||
viewport: {
|
||||
width: 400,
|
||||
height: 400,
|
||||
|
@ -60,7 +59,7 @@ it.describe('device', () => {
|
|||
await context.close();
|
||||
});
|
||||
|
||||
it('should scroll twice when emulated', async ({ server, contextFactory, playwright }) => {
|
||||
it('should scroll twice when emulated', async ({ contextFactory, playwright }) => {
|
||||
const device = playwright.devices['iPhone 6'];
|
||||
const context = await contextFactory(device);
|
||||
const page = await context.newPage();
|
||||
|
@ -105,7 +104,7 @@ it.describe('device', () => {
|
|||
await context.close();
|
||||
});
|
||||
|
||||
it('should emulate viewport and screen size', async ({ server, contextFactory, playwright }) => {
|
||||
it('should emulate viewport and screen size', async ({ contextFactory, playwright }) => {
|
||||
const device = playwright.devices['iPhone 12'];
|
||||
const context = await contextFactory(device);
|
||||
const page = await context.newPage();
|
||||
|
@ -124,7 +123,7 @@ it.describe('device', () => {
|
|||
await context.close();
|
||||
});
|
||||
|
||||
it('should emulate viewport without screen size', async ({ server, contextFactory, playwright }) => {
|
||||
it('should emulate viewport without screen size', async ({ contextFactory, playwright }) => {
|
||||
const device = playwright.devices['iPhone 6'];
|
||||
const context = await contextFactory(device);
|
||||
const page = await context.newPage();
|
||||
|
|
|
@ -47,7 +47,7 @@ it('should throw for missing global proxy on Chromium Windows', async ({ browser
|
|||
}
|
||||
});
|
||||
|
||||
it('should work when passing the proxy only on the context level', async ({ browserName, platform, browserType, contextOptions, server, proxyServer }) => {
|
||||
it('should work when passing the proxy only on the context level', async ({ browserName, platform, browserType, server, proxyServer }) => {
|
||||
// Currently an upstream bug in the network stack of Chromium which leads that
|
||||
// the wrong proxy gets used in the BrowserContext.
|
||||
it.fixme(browserName === 'chromium' && platform === 'win32');
|
||||
|
@ -59,7 +59,6 @@ it('should work when passing the proxy only on the context level', async ({ brow
|
|||
proxy: undefined,
|
||||
});
|
||||
const context = await browser.newContext({
|
||||
...contextOptions,
|
||||
proxy: { server: `localhost:${proxyServer.PORT}` }
|
||||
});
|
||||
|
||||
|
|
|
@ -16,10 +16,9 @@
|
|||
|
||||
import type { Config, PlaywrightTestOptions, PlaywrightWorkerOptions } from '@playwright/test';
|
||||
import * as path from 'path';
|
||||
import { test as pageTest } from '../page/pageTest';
|
||||
import { androidFixtures } from '../android/androidTest';
|
||||
import { ServerWorkerOptions } from './serverFixtures';
|
||||
import { playwrightFixtures } from './browserTest';
|
||||
|
||||
process.env.PWPAGE_IMPL = 'android';
|
||||
|
||||
const outputDir = path.join(__dirname, '..', '..', 'test-results');
|
||||
const testDir = path.join(__dirname, '..');
|
||||
|
@ -65,7 +64,6 @@ config.projects.push({
|
|||
browserName: 'chromium',
|
||||
},
|
||||
testDir: path.join(testDir, 'page'),
|
||||
define: { test: pageTest, fixtures: { ...playwrightFixtures, ...androidFixtures } },
|
||||
metadata,
|
||||
});
|
||||
|
||||
|
|
|
@ -14,16 +14,24 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { _baseTest } from '@playwright/test';
|
||||
import { test } from '@playwright/test';
|
||||
import { commonFixtures, CommonFixtures } from './commonFixtures';
|
||||
import { serverFixtures, ServerFixtures, ServerWorkerOptions } from './serverFixtures';
|
||||
import { coverageFixtures, CoverageWorkerOptions } from './coverageFixtures';
|
||||
import { platformFixtures, PlatformWorkerFixtures } from './platformFixtures';
|
||||
import { testModeFixtures, TestModeWorkerFixtures } from './testModeFixtures';
|
||||
|
||||
export const baseTest = _baseTest
|
||||
.extend<{}, CoverageWorkerOptions>(coverageFixtures)
|
||||
|
||||
export type BaseTestWorkerFixtures = {
|
||||
_snapshotSuffix: string;
|
||||
};
|
||||
|
||||
export const baseTest = test
|
||||
.extend<{}, CoverageWorkerOptions>(coverageFixtures as any)
|
||||
.extend<{}, PlatformWorkerFixtures>(platformFixtures)
|
||||
.extend<{}, TestModeWorkerFixtures>(testModeFixtures)
|
||||
.extend<{}, TestModeWorkerFixtures>(testModeFixtures as any)
|
||||
.extend<CommonFixtures>(commonFixtures)
|
||||
.extend<ServerFixtures, ServerWorkerOptions>(serverFixtures as any);
|
||||
.extend<ServerFixtures, ServerWorkerOptions>(serverFixtures as any)
|
||||
.extend<{}, BaseTestWorkerFixtures>({
|
||||
_snapshotSuffix: ['', { scope: 'worker' }],
|
||||
});
|
||||
|
|
|
@ -14,70 +14,49 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Fixtures, PlaywrightTestOptions, PlaywrightWorkerOptions } from '@playwright/test';
|
||||
import type { Browser, BrowserContext, BrowserContextOptions, BrowserType, LaunchOptions, Page } from 'playwright-core';
|
||||
import { removeFolders } from 'playwright-core/lib/utils/utils';
|
||||
import { browserOptionsWorkerFixture, browserTypeWorkerFixture, browserWorkerFixture, ReuseBrowserContextStorage } from '../../packages/playwright-test/lib/index';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import { RemoteServer, RemoteServerOptions } from './remoteServer';
|
||||
import { PageTestFixtures, PageWorkerFixtures } from '../page/pageTestApi';
|
||||
import * as path from 'path';
|
||||
import type { BrowserContext, BrowserContextOptions, BrowserType, Page } from 'playwright-core';
|
||||
import { removeFolders } from 'playwright-core/lib/utils/utils';
|
||||
import { baseTest } from './baseTest';
|
||||
import { CommonFixtures } from './commonFixtures';
|
||||
import type { ParsedStackTrace } from 'playwright-core/lib/utils/stackTrace';
|
||||
import { DefaultTestMode, DriverTestMode, ServiceTestMode } from './testMode';
|
||||
import { TestModeWorkerFixtures } from './testModeFixtures';
|
||||
import { RemoteServer, RemoteServerOptions } from './remoteServer';
|
||||
|
||||
export type PlaywrightWorkerFixtures = {
|
||||
playwright: typeof import('playwright-core');
|
||||
_browserType: BrowserType;
|
||||
_browserOptions: LaunchOptions;
|
||||
browserType: BrowserType;
|
||||
browser: Browser;
|
||||
export type BrowserTestWorkerFixtures = PageWorkerFixtures & {
|
||||
browserVersion: string;
|
||||
_reuseBrowserContext: ReuseBrowserContextStorage;
|
||||
toImpl: (rpcObject: any) => any;
|
||||
browserMajorVersion: number;
|
||||
browserType: BrowserType;
|
||||
isAndroid: boolean;
|
||||
isElectron: boolean;
|
||||
};
|
||||
|
||||
type PlaywrightTestFixtures = {
|
||||
type BrowserTestTestFixtures = PageTestFixtures & {
|
||||
createUserDataDir: () => Promise<string>;
|
||||
launchPersistent: (options?: Parameters<BrowserType['launchPersistentContext']>[1]) => Promise<{ context: BrowserContext, page: Page }>;
|
||||
startRemoteServer: (options?: RemoteServerOptions) => Promise<RemoteServer>;
|
||||
contextOptions: BrowserContextOptions;
|
||||
contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>;
|
||||
context: BrowserContext;
|
||||
page: Page;
|
||||
};
|
||||
|
||||
export const playwrightFixtures: Fixtures<PlaywrightTestOptions & PlaywrightTestFixtures, PlaywrightWorkerOptions & PlaywrightWorkerFixtures, CommonFixtures, TestModeWorkerFixtures> = {
|
||||
hasTouch: undefined,
|
||||
|
||||
playwright: [ async ({ mode }, run) => {
|
||||
const testMode = {
|
||||
default: new DefaultTestMode(),
|
||||
service: new ServiceTestMode(),
|
||||
driver: new DriverTestMode(),
|
||||
}[mode];
|
||||
require('playwright-core/lib/utils/utils').setUnderTest();
|
||||
const playwright = await testMode.setup();
|
||||
await run(playwright);
|
||||
await testMode.teardown();
|
||||
}, { scope: 'worker' } ],
|
||||
|
||||
toImpl: [ async ({ playwright }, run) => run((playwright as any)._toImpl), { scope: 'worker' } ],
|
||||
|
||||
_browserType: [browserTypeWorkerFixture, { scope: 'worker' } ],
|
||||
_browserOptions: [browserOptionsWorkerFixture, { scope: 'worker' } ],
|
||||
|
||||
launchOptions: [ {}, { scope: 'worker' } ],
|
||||
browserType: [async ({ _browserType }, use) => use(_browserType), { scope: 'worker' } ],
|
||||
browser: [browserWorkerFixture, { scope: 'worker' } ],
|
||||
|
||||
const test = baseTest.extend<BrowserTestTestFixtures, BrowserTestWorkerFixtures>({
|
||||
browserVersion: [async ({ browser }, run) => {
|
||||
await run(browser.version());
|
||||
}, { scope: 'worker' } ],
|
||||
|
||||
_reuseBrowserContext: [new ReuseBrowserContextStorage(), { scope: 'worker' }],
|
||||
browserType: [async ({ _browserType }: any, run) => {
|
||||
await run(_browserType);
|
||||
}, { scope: 'worker' } ],
|
||||
|
||||
browserMajorVersion: [async ({ browserVersion }, run) => {
|
||||
await run(Number(browserVersion.split('.')[0]));
|
||||
}, { scope: 'worker' } ],
|
||||
|
||||
isAndroid: [false, { scope: 'worker' } ],
|
||||
isElectron: [false, { scope: 'worker' } ],
|
||||
|
||||
contextFactory: async ({ _contextFactory }: any, run) => {
|
||||
await run(_contextFactory);
|
||||
},
|
||||
|
||||
createUserDataDir: async ({}, run) => {
|
||||
const dirs: string[] = [];
|
||||
|
@ -99,7 +78,7 @@ export const playwrightFixtures: Fixtures<PlaywrightTestOptions & PlaywrightTest
|
|||
let persistentContext: BrowserContext | undefined;
|
||||
await run(async options => {
|
||||
if (persistentContext)
|
||||
throw new Error('can only launch one persitent context');
|
||||
throw new Error('can only launch one persistent context');
|
||||
const userDataDir = await createUserDataDir();
|
||||
persistentContext = await browserType.launchPersistentContext(userDataDir, { ...options });
|
||||
const page = persistentContext.pages()[0];
|
||||
|
@ -121,90 +100,8 @@ export const playwrightFixtures: Fixtures<PlaywrightTestOptions & PlaywrightTest
|
|||
if (remoteServer)
|
||||
await remoteServer.close();
|
||||
},
|
||||
});
|
||||
|
||||
contextOptions: async ({ video, hasTouch }, run, testInfo) => {
|
||||
const debugName = path.relative(testInfo.project.outputDir, testInfo.outputDir).replace(/[\/\\]/g, '-');
|
||||
const contextOptions = {
|
||||
recordVideo: video === 'on' ? { dir: testInfo.outputPath('') } : undefined,
|
||||
_debugName: debugName,
|
||||
hasTouch,
|
||||
} as BrowserContextOptions;
|
||||
await run(contextOptions);
|
||||
},
|
||||
|
||||
contextFactory: async ({ browser, contextOptions, trace }, run, testInfo) => {
|
||||
const contexts = new Map<BrowserContext, { closed: boolean }>();
|
||||
await run(async options => {
|
||||
const context = await browser.newContext({ ...contextOptions, ...options });
|
||||
contexts.set(context, { closed: false });
|
||||
context.on('close', () => contexts.get(context).closed = true);
|
||||
if (trace === 'on')
|
||||
await context.tracing.start({ screenshots: true, snapshots: true, sources: true } as any);
|
||||
(context as any)._instrumentation.addListener({
|
||||
onApiCallBegin: (apiCall: string, stackTrace: ParsedStackTrace | null, userData: any) => {
|
||||
if (apiCall.startsWith('expect.'))
|
||||
return { userObject: null };
|
||||
const testInfoImpl = testInfo as any;
|
||||
const step = testInfoImpl._addStep({
|
||||
location: stackTrace?.frames[0],
|
||||
category: 'pw:api',
|
||||
title: apiCall,
|
||||
canHaveChildren: false,
|
||||
forceNoParent: false
|
||||
});
|
||||
userData.userObject = step;
|
||||
},
|
||||
onApiCallEnd: (userData: any, error?: Error) => {
|
||||
const step = userData.userObject;
|
||||
step?.complete(error);
|
||||
},
|
||||
});
|
||||
return context;
|
||||
});
|
||||
await Promise.all([...contexts.keys()].map(async context => {
|
||||
const videos = context.pages().map(p => p.video()).filter(Boolean);
|
||||
if (trace === 'on' && !contexts.get(context)!.closed) {
|
||||
const tracePath = testInfo.outputPath('trace.zip');
|
||||
await context.tracing.stop({ path: tracePath });
|
||||
testInfo.attachments.push({ name: 'trace', path: tracePath, contentType: 'application/zip' });
|
||||
}
|
||||
await context.close();
|
||||
for (const v of videos) {
|
||||
const videoPath = await v.path().catch(() => null);
|
||||
if (!videoPath)
|
||||
continue;
|
||||
const savedPath = testInfo.outputPath(path.basename(videoPath));
|
||||
await v.saveAs(savedPath);
|
||||
testInfo.attachments.push({ name: 'video', path: savedPath, contentType: 'video/webm' });
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
context: async ({ contextFactory, browser, _reuseBrowserContext, contextOptions }, run) => {
|
||||
if (_reuseBrowserContext.isEnabled()) {
|
||||
const context = await _reuseBrowserContext.obtainContext(browser, contextOptions);
|
||||
await run(context);
|
||||
return;
|
||||
}
|
||||
await run(await contextFactory());
|
||||
},
|
||||
|
||||
page: async ({ context, _reuseBrowserContext }, run) => {
|
||||
if (_reuseBrowserContext.isEnabled()) {
|
||||
await run(await _reuseBrowserContext.obtainPage());
|
||||
return;
|
||||
}
|
||||
await run(await context.newPage());
|
||||
},
|
||||
|
||||
browserName: [ 'chromium' , { scope: 'worker' } ],
|
||||
headless: [ undefined, { scope: 'worker' } ],
|
||||
channel: [ undefined, { scope: 'worker' } ],
|
||||
video: [ 'off', { scope: 'worker' } ],
|
||||
trace: [ 'off', { scope: 'worker' } ],
|
||||
};
|
||||
|
||||
const test = baseTest.extend<PlaywrightTestOptions & PlaywrightTestFixtures, PlaywrightWorkerOptions & PlaywrightWorkerFixtures>(playwrightFixtures);
|
||||
export const playwrightTest = test;
|
||||
export const browserTest = test;
|
||||
export const contextTest = test;
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
import type { Config, PlaywrightTestOptions, PlaywrightWorkerOptions } from '@playwright/test';
|
||||
import * as path from 'path';
|
||||
import { playwrightFixtures } from './browserTest';
|
||||
import { test as pageTest } from '../page/pageTest';
|
||||
import { TestModeWorkerFixtures } from './testModeFixtures';
|
||||
import { CoverageWorkerOptions } from './coverageFixtures';
|
||||
|
||||
|
@ -32,15 +30,6 @@ const getExecutablePath = (browserName: BrowserName) => {
|
|||
return process.env.WKPATH;
|
||||
};
|
||||
|
||||
const pageFixtures = {
|
||||
...playwrightFixtures,
|
||||
browserMajorVersion: async ({ browserVersion }, run) => {
|
||||
await run(Number(browserVersion.split('.')[0]));
|
||||
},
|
||||
isAndroid: false,
|
||||
isElectron: false,
|
||||
};
|
||||
|
||||
const mode = (process.env.PWTEST_MODE || 'default') as ('default' | 'driver' | 'service');
|
||||
const headed = !!process.env.HEADFUL;
|
||||
const channel = process.env.PWTEST_CHANNEL as any;
|
||||
|
@ -95,7 +84,6 @@ for (const browserName of browserNames) {
|
|||
trace: trace ? 'on' : undefined,
|
||||
coverageName: browserName,
|
||||
},
|
||||
define: { test: pageTest, fixtures: pageFixtures },
|
||||
metadata: {
|
||||
platform: process.platform,
|
||||
docker: !!process.env.INSIDE_DOCKER,
|
||||
|
|
|
@ -16,11 +16,10 @@
|
|||
|
||||
import type { Config, PlaywrightTestOptions, PlaywrightWorkerOptions } from '@playwright/test';
|
||||
import * as path from 'path';
|
||||
import { electronFixtures } from '../electron/electronTest';
|
||||
import { test as pageTest } from '../page/pageTest';
|
||||
import { playwrightFixtures } from './browserTest';
|
||||
import { CoverageWorkerOptions } from './coverageFixtures';
|
||||
|
||||
process.env.PWPAGE_IMPL = 'electron';
|
||||
|
||||
const outputDir = path.join(__dirname, '..', '..', 'test-results');
|
||||
const testDir = path.join(__dirname, '..');
|
||||
const config: Config<CoverageWorkerOptions & PlaywrightWorkerOptions & PlaywrightTestOptions> = {
|
||||
|
@ -65,7 +64,6 @@ config.projects.push({
|
|||
coverageName: 'electron',
|
||||
},
|
||||
testDir: path.join(testDir, 'page'),
|
||||
define: { test: pageTest, fixtures: { ...playwrightFixtures, ...electronFixtures } },
|
||||
metadata,
|
||||
});
|
||||
|
||||
|
|
|
@ -14,12 +14,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ElectronApplication, Page } from 'playwright-core';
|
||||
import type { Fixtures, PlaywrightWorkerOptions } from '@playwright/test';
|
||||
import { baseTest } from '../config/baseTest';
|
||||
import * as path from 'path';
|
||||
import { PageTestFixtures } from '../page/pageTest';
|
||||
import { TestModeWorkerFixtures } from '../config/testModeFixtures';
|
||||
import { browserTest } from '../config/browserTest';
|
||||
import { ElectronApplication, Page } from 'playwright-core';
|
||||
import { PageTestFixtures, PageWorkerFixtures } from '../page/pageTestApi';
|
||||
export { expect } from '@playwright/test';
|
||||
|
||||
type ElectronTestFixtures = PageTestFixtures & {
|
||||
|
@ -28,11 +26,12 @@ type ElectronTestFixtures = PageTestFixtures & {
|
|||
};
|
||||
|
||||
const electronVersion = require('electron/package.json').version;
|
||||
export const electronFixtures: Fixtures<ElectronTestFixtures, {}, {}, PlaywrightWorkerOptions & TestModeWorkerFixtures> = {
|
||||
browserVersion: electronVersion,
|
||||
browserMajorVersion: Number(electronVersion.split('.')[0]),
|
||||
isAndroid: false,
|
||||
isElectron: true,
|
||||
|
||||
export const electronTest = baseTest.extend<ElectronTestFixtures, PageWorkerFixtures>({
|
||||
browserVersion: [electronVersion, { scope: 'worker' }],
|
||||
browserMajorVersion: [Number(electronVersion.split('.')[0]), { scope: 'worker' }],
|
||||
isAndroid: [false, { scope: 'worker' }],
|
||||
isElectron: [true, { scope: 'worker' }],
|
||||
|
||||
electronApp: async ({ playwright }, run) => {
|
||||
// This env prevents 'Electron Security Policy' console message.
|
||||
|
@ -70,6 +69,4 @@ export const electronFixtures: Fixtures<ElectronTestFixtures, {}, {}, Playwright
|
|||
page: async ({ newWindow }, run) => {
|
||||
await run(await newWindow());
|
||||
},
|
||||
};
|
||||
|
||||
export const electronTest = browserTest.extend<ElectronTestFixtures>(electronFixtures as any);
|
||||
});
|
||||
|
|
|
@ -168,8 +168,8 @@ it('should set playwright as user-agent', async ({ playwright, server }) => {
|
|||
expect(serverRequest.headers['user-agent']).toBe('Playwright/' + getPlaywrightVersion());
|
||||
});
|
||||
|
||||
it('should be able to construct with context options', async ({ playwright, server, contextOptions }) => {
|
||||
const request = await playwright.request.newContext(contextOptions);
|
||||
it('should be able to construct with context options', async ({ playwright, browserType, server }) => {
|
||||
const request = await playwright.request.newContext((browserType as any)._defaultContextOptions);
|
||||
const response = await request.get(server.EMPTY_PAGE);
|
||||
expect(response.ok()).toBeTruthy();
|
||||
});
|
||||
|
|
|
@ -244,7 +244,8 @@ it('should work when page calls history API in beforeunload', async ({ page, ser
|
|||
expect(response.status()).toBe(200);
|
||||
});
|
||||
|
||||
it('should fail when navigating to bad url', async ({ page, browserName }) => {
|
||||
it('should fail when navigating to bad url', async ({ mode, page, browserName }) => {
|
||||
it.fixme(mode === 'service', 'baseURL is inherited from webServer in config');
|
||||
let error = null;
|
||||
await page.goto('asdfasdf').catch(e => error = e);
|
||||
if (browserName === 'chromium' || browserName === 'webkit')
|
||||
|
@ -347,6 +348,8 @@ it('should fail when exceeding default maximum timeout', async ({ page, server,
|
|||
// Hang for request to the empty.html
|
||||
server.setRoute('/empty.html', (req, res) => { });
|
||||
let error = null;
|
||||
// Undo what harness did.
|
||||
page.context().setDefaultNavigationTimeout(undefined);
|
||||
page.context().setDefaultTimeout(2);
|
||||
page.setDefaultTimeout(1);
|
||||
await page.goto(server.PREFIX + '/empty.html').catch(e => error = e);
|
||||
|
@ -361,6 +364,8 @@ it('should fail when exceeding browser context timeout', async ({ page, server,
|
|||
// Hang for request to the empty.html
|
||||
server.setRoute('/empty.html', (req, res) => { });
|
||||
let error = null;
|
||||
// Undo what harness did.
|
||||
page.context().setDefaultNavigationTimeout(undefined);
|
||||
page.context().setDefaultTimeout(2);
|
||||
await page.goto(server.PREFIX + '/empty.html').catch(e => error = e);
|
||||
expect(error.message).toContain('page.goto: Timeout 2ms exceeded.');
|
||||
|
|
|
@ -14,26 +14,21 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { baseTest } from '../config/baseTest';
|
||||
import type { Page, ViewportSize } from 'playwright-core';
|
||||
import { VideoMode } from '@playwright/test';
|
||||
import { TestType } from '@playwright/test';
|
||||
import { PlatformWorkerFixtures } from '../config/platformFixtures';
|
||||
import { TestModeWorkerFixtures } from '../config/testModeFixtures';
|
||||
import { androidTest } from '../android/androidTest';
|
||||
import { browserTest } from '../config/browserTest';
|
||||
import { electronTest } from '../electron/electronTest';
|
||||
import { PageTestFixtures, PageWorkerFixtures } from './pageTestApi';
|
||||
import { ServerFixtures, ServerWorkerOptions } from '../config/serverFixtures';
|
||||
export { expect } from '@playwright/test';
|
||||
|
||||
// Page test does not guarantee an isolated context, just a new page (because Android).
|
||||
export type PageTestFixtures = {
|
||||
browserVersion: string;
|
||||
browserMajorVersion: number;
|
||||
page: Page;
|
||||
isAndroid: boolean;
|
||||
isElectron: boolean;
|
||||
};
|
||||
let impl: TestType<PageTestFixtures & ServerFixtures, PageWorkerFixtures & PlatformWorkerFixtures & TestModeWorkerFixtures & ServerWorkerOptions> = browserTest;
|
||||
|
||||
export type PageWorkerFixtures = {
|
||||
headless: boolean,
|
||||
channel: string,
|
||||
trace: 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | /** deprecated */ 'retry-with-trace';
|
||||
video: VideoMode | { mode: VideoMode, size: ViewportSize };
|
||||
browserName: 'chromium' | 'firefox' | 'webkit',
|
||||
};
|
||||
if (process.env.PWPAGE_IMPL === 'android')
|
||||
impl = androidTest;
|
||||
if (process.env.PWPAGE_IMPL === 'electron')
|
||||
impl = electronTest;
|
||||
|
||||
export const test = baseTest.declare<PageTestFixtures, PageWorkerFixtures>();
|
||||
export const test = impl;
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* 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, ViewportSize } from 'playwright-core';
|
||||
import { VideoMode } from '@playwright/test';
|
||||
export { expect } from '@playwright/test';
|
||||
|
||||
// Page test does not guarantee an isolated context, just a new page (because Android).
|
||||
export type PageTestFixtures = {
|
||||
page: Page;
|
||||
};
|
||||
|
||||
export type PageWorkerFixtures = {
|
||||
headless: boolean;
|
||||
channel: string;
|
||||
trace: 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | /** deprecated */ 'retry-with-trace';
|
||||
video: VideoMode | { mode: VideoMode, size: ViewportSize };
|
||||
browserName: 'chromium' | 'firefox' | 'webkit';
|
||||
browserVersion: string;
|
||||
browserMajorVersion: number;
|
||||
isAndroid: boolean;
|
||||
isElectron: boolean;
|
||||
};
|
|
@ -456,83 +456,3 @@ test('should work with video size', async ({ runInlineTest }, testInfo) => {
|
|||
expect(videoPlayer.videoWidth).toBe(220);
|
||||
expect(videoPlayer.videoHeight).toBe(110);
|
||||
});
|
||||
|
||||
test('should be able to re-use the context when debug mode is used', async ({ runInlineTest }, testInfo) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
const { test } = pwt;
|
||||
|
||||
test.use({
|
||||
colorScheme: 'light',
|
||||
viewport: {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
},
|
||||
})
|
||||
|
||||
const host1 = 'http://host1.com/foobar';
|
||||
|
||||
test.beforeEach(async({page, context}) => {
|
||||
context.route(host1, route => route.fulfill({body: '<html></html>', contentType: 'text/html'}, {times: 1}));
|
||||
console.log(page._guid + '|');
|
||||
console.log(context._guid + '|');
|
||||
})
|
||||
|
||||
test('initial setup', async ({ page }) => {
|
||||
await page.goto(host1);
|
||||
expect(await page.evaluate(() => window.localStorage.getItem('foobar'))).toBe(null);
|
||||
await page.evaluate(() => window.localStorage.setItem('foobar', 'bar'));
|
||||
expect(page.viewportSize()).toStrictEqual({
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
});
|
||||
|
||||
test('second run after persistent data has changed', async ({ page }) => {
|
||||
await page.goto(host1);
|
||||
expect(await page.evaluate(() => window.localStorage.getItem('foobar'))).toBe(null);
|
||||
await page.evaluate(() => window.localStorage.setItem('foobar', 'bar'));
|
||||
expect(page.viewportSize()).toStrictEqual({
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('inside a describe block', () => {
|
||||
test.use({
|
||||
colorScheme: 'dark',
|
||||
viewport: {
|
||||
width: 1000,
|
||||
height: 500,
|
||||
},
|
||||
});
|
||||
test('using different options', async ({ page }) => {
|
||||
await page.goto(host1);
|
||||
expect(await page.evaluate(() => window.localStorage.getItem('foobar'))).toBe(null);
|
||||
expect(page.viewportSize()).toStrictEqual({
|
||||
width: 1000,
|
||||
height: 500,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('after the describe block', async ({ page }) => {
|
||||
await page.goto(host1);
|
||||
expect(await page.evaluate(() => window.localStorage.getItem('foobar'))).toBe(null);
|
||||
expect(page.viewportSize()).toStrictEqual({
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
});
|
||||
`
|
||||
}, { '--reuse-context': true });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(4);
|
||||
const pageIds = result.output.match(/page@(.*)\|/g);
|
||||
const browserContextIds = result.output.match(/browser-context@(.*)\|/g);
|
||||
expect(pageIds.length).toBe(4);
|
||||
expect(new Set(pageIds).size).toBe(1);
|
||||
expect(browserContextIds.length).toBe(4);
|
||||
expect(new Set(browserContextIds).size).toBe(1);
|
||||
});
|
||||
|
||||
|
|
|
@ -530,12 +530,11 @@ it.describe('screencast', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('should emulate an iphone', async ({ contextFactory, playwright, contextOptions, browserName }, testInfo) => {
|
||||
it('should emulate an iphone', async ({ contextFactory, playwright, browserName }, testInfo) => {
|
||||
it.skip(browserName === 'firefox', 'isMobile is not supported in Firefox');
|
||||
|
||||
const device = playwright.devices['iPhone 6'];
|
||||
const context = await contextFactory({
|
||||
...contextOptions,
|
||||
...device,
|
||||
recordVideo: {
|
||||
dir: testInfo.outputPath(''),
|
||||
|
@ -552,11 +551,10 @@ it.describe('screencast', () => {
|
|||
expect(videoPlayer.videoHeight).toBe(666);
|
||||
});
|
||||
|
||||
it('should throw on browser close', async ({ browserType, contextOptions }, testInfo) => {
|
||||
it('should throw on browser close', async ({ browserType }, testInfo) => {
|
||||
const size = { width: 320, height: 240 };
|
||||
const browser = await browserType.launch();
|
||||
const context = await browser.newContext({
|
||||
...contextOptions,
|
||||
recordVideo: {
|
||||
dir: testInfo.outputPath(''),
|
||||
size,
|
||||
|
@ -573,12 +571,11 @@ it.describe('screencast', () => {
|
|||
expect(saveResult.message).toContain('browser has been closed');
|
||||
});
|
||||
|
||||
it('should throw if browser dies', async ({ browserType, contextOptions }, testInfo) => {
|
||||
it('should throw if browser dies', async ({ browserType }, testInfo) => {
|
||||
const size = { width: 320, height: 240 };
|
||||
const browser = await browserType.launch();
|
||||
|
||||
const context = await browser.newContext({
|
||||
...contextOptions,
|
||||
recordVideo: {
|
||||
dir: testInfo.outputPath(''),
|
||||
size,
|
||||
|
@ -595,13 +592,12 @@ it.describe('screencast', () => {
|
|||
expect(saveResult.message).toContain('rowser has been closed');
|
||||
});
|
||||
|
||||
it('should wait for video to finish if page was closed', async ({ browserType, contextOptions }, testInfo) => {
|
||||
it('should wait for video to finish if page was closed', async ({ browserType }, testInfo) => {
|
||||
const size = { width: 320, height: 240 };
|
||||
const browser = await browserType.launch();
|
||||
|
||||
const videoDir = testInfo.outputPath('');
|
||||
const context = await browser.newContext({
|
||||
...contextOptions,
|
||||
recordVideo: {
|
||||
dir: videoDir,
|
||||
size,
|
||||
|
@ -622,7 +618,7 @@ it.describe('screencast', () => {
|
|||
expect(videoPlayer.videoHeight).toBe(240);
|
||||
});
|
||||
|
||||
it('should not create video for internal pages', async ({ browser, browserName, contextOptions, server }, testInfo) => {
|
||||
it('should not create video for internal pages', async ({ browser, server }, testInfo) => {
|
||||
it.fixme(true, 'https://github.com/microsoft/playwright/issues/6743');
|
||||
server.setRoute('/empty.html', (req, res) => {
|
||||
res.setHeader('Set-Cookie', 'name=value');
|
||||
|
@ -631,7 +627,6 @@ it.describe('screencast', () => {
|
|||
|
||||
const videoDir = testInfo.outputPath('');
|
||||
const context = await browser.newContext({
|
||||
...contextOptions,
|
||||
recordVideo: {
|
||||
dir: videoDir
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче