chore: introduce clock test mode (#31110)
This commit is contained in:
parent
afa0bf2247
commit
8bfd0eb6e4
62
.github/workflows/tests_clock.yml
vendored
Normal file
62
.github/workflows/tests_clock.yml
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
name: "tests Clock"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release-*
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'browser_patches/**'
|
||||
- 'docs/**'
|
||||
types: [ labeled ]
|
||||
branches:
|
||||
- main
|
||||
- release-*
|
||||
|
||||
env:
|
||||
# Force terminal colors. @see https://www.npmjs.com/package/colors
|
||||
FORCE_COLOR: 1
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
|
||||
|
||||
jobs:
|
||||
frozen_time_linux:
|
||||
name: Frozen time library
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
permissions:
|
||||
id-token: write # This is required for OIDC login (azure/login) to succeed
|
||||
contents: read # This is required for actions/checkout to succeed
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
node-version: 20
|
||||
browsers-to-install: chromium
|
||||
command: npm run test -- --project=chromium-*
|
||||
bot-name: "frozen-time-library-chromium-linux"
|
||||
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
|
||||
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
|
||||
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
|
||||
env:
|
||||
PW_FREEZE_TIME: 1
|
||||
|
||||
frozen_time_test_runner:
|
||||
name: Frozen time test runner
|
||||
environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }}
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
id-token: write # This is required for OIDC login (azure/login) to succeed
|
||||
contents: read # This is required for actions/checkout to succeed
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/run-test
|
||||
with:
|
||||
node-version: 20
|
||||
command: npm run ttest
|
||||
bot-name: "frozen-time-runner-chromium-linux"
|
||||
flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }}
|
||||
flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }}
|
||||
flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }}
|
||||
env:
|
||||
PW_FREEZE_TIME: 1
|
||||
|
|
@ -21,7 +21,7 @@ Install fake timers with the specified unix epoch (default: 0).
|
|||
- `toFake` <[Array]<[FakeMethod]<"setTimeout"|"clearTimeout"|"setInterval"|"clearInterval"|"Date"|"requestAnimationFrame"|"cancelAnimationFrame"|"requestIdleCallback"|"cancelIdleCallback"|"performance">>>
|
||||
|
||||
An array with names of global methods and APIs to fake. For instance, `await page.clock.install({ toFake: ['setTimeout'] })` will fake only `setTimeout()`.
|
||||
By default, `setTimeout`, `clearTimeout`, `setInterval`, `clearInterval` and `Date` are faked.
|
||||
By default, all the methods are faked.
|
||||
|
||||
### option: Clock.install.loopLimit
|
||||
* since: v1.45
|
||||
|
|
|
|||
|
|
@ -85,6 +85,8 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
|
|||
const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
|
||||
const context = BrowserContext.from(response.context);
|
||||
await this._browserType._didCreateContext(context, contextOptions, this._options, options.logger || this._logger);
|
||||
if (!forReuse && !!process.env.PW_FREEZE_TIME)
|
||||
await this._wrapApiCall(async () => { await context.clock.install(); }, true);
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel> implements api.Fr
|
|||
|
||||
async _evaluateExposeUtilityScript<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<R> {
|
||||
assertMaxArguments(arguments.length, 2);
|
||||
const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', exposeUtilityScript: true, arg: serializeArgument(arg) });
|
||||
const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
|
||||
return parseResult(result.value);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1429,7 +1429,6 @@ scheme.FrameDispatchEventResult = tOptional(tObject({}));
|
|||
scheme.FrameEvaluateExpressionParams = tObject({
|
||||
expression: tString,
|
||||
isFunction: tOptional(tBoolean),
|
||||
exposeUtilityScript: tOptional(tBoolean),
|
||||
arg: tType('SerializedArgument'),
|
||||
});
|
||||
scheme.FrameEvaluateExpressionResult = tObject({
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel, Br
|
|||
}
|
||||
|
||||
async evaluateExpression(params: channels.FrameEvaluateExpressionParams, metadata: CallMetadata): Promise<channels.FrameEvaluateExpressionResult> {
|
||||
return { value: serializeResult(await this._frame.evaluateExpression(params.expression, { isFunction: params.isFunction, exposeUtilityScript: params.exposeUtilityScript }, parseArgument(params.arg))) };
|
||||
return { value: serializeResult(await this._frame.evaluateExpression(params.expression, { isFunction: params.isFunction }, parseArgument(params.arg))) };
|
||||
}
|
||||
|
||||
async evaluateExpressionHandle(params: channels.FrameEvaluateExpressionHandleParams, metadata: CallMetadata): Promise<channels.FrameEvaluateExpressionHandleResult> {
|
||||
|
|
|
|||
|
|
@ -71,11 +71,11 @@ export class FrameExecutionContext extends js.ExecutionContext {
|
|||
return js.evaluate(this, false /* returnByValue */, pageFunction, arg);
|
||||
}
|
||||
|
||||
async evaluateExpression(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean }, arg?: any): Promise<any> {
|
||||
async evaluateExpression(expression: string, options: { isFunction?: boolean }, arg?: any): Promise<any> {
|
||||
return js.evaluateExpression(this, expression, { ...options, returnByValue: true }, arg);
|
||||
}
|
||||
|
||||
async evaluateExpressionHandle(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean }, arg?: any): Promise<js.JSHandle<any>> {
|
||||
async evaluateExpressionHandle(expression: string, options: { isFunction?: boolean }, arg?: any): Promise<js.JSHandle<any>> {
|
||||
return js.evaluateExpression(this, expression, { ...options, returnByValue: false }, arg);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -745,13 +745,13 @@ export class Frame extends SdkObject {
|
|||
return this._context('utility');
|
||||
}
|
||||
|
||||
async evaluateExpression(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean, world?: types.World } = {}, arg?: any): Promise<any> {
|
||||
async evaluateExpression(expression: string, options: { isFunction?: boolean, world?: types.World } = {}, arg?: any): Promise<any> {
|
||||
const context = await this._context(options.world ?? 'main');
|
||||
const value = await context.evaluateExpression(expression, options, arg);
|
||||
return value;
|
||||
}
|
||||
|
||||
async evaluateExpressionHandle(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean, world?: types.World } = {}, arg?: any): Promise<js.JSHandle<any>> {
|
||||
async evaluateExpressionHandle(expression: string, options: { isFunction?: boolean, world?: types.World } = {}, arg?: any): Promise<js.JSHandle<any>> {
|
||||
const context = await this._context(options.world ?? 'main');
|
||||
const value = await context.evaluateExpressionHandle(expression, options, arg);
|
||||
return value;
|
||||
|
|
@ -1513,9 +1513,9 @@ export class Frame extends SdkObject {
|
|||
return;
|
||||
}
|
||||
if (typeof polling !== 'number')
|
||||
requestAnimationFrame(next);
|
||||
injected.builtinRequestAnimationFrame(next);
|
||||
else
|
||||
setTimeout(next, polling);
|
||||
injected.builtinSetTimeout(next, polling);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,5 +19,22 @@ import SinonFakeTimers from '../../third_party/fake-timers-src';
|
|||
import type * as channels from '@protocol/channels';
|
||||
|
||||
export function install(params: channels.BrowserContextClockInstallOptions) {
|
||||
return SinonFakeTimers.install(params);
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const window = globalThis;
|
||||
const builtin = {
|
||||
setTimeout: window.setTimeout.bind(window),
|
||||
clearTimeout: window.clearTimeout.bind(window),
|
||||
setInterval: window.setInterval.bind(window),
|
||||
clearInterval: window.clearInterval.bind(window),
|
||||
requestAnimationFrame: window.requestAnimationFrame.bind(window),
|
||||
cancelAnimationFrame: window.cancelAnimationFrame.bind(window),
|
||||
requestIdleCallback: window.requestIdleCallback?.bind(window),
|
||||
cancelIdleCallback: window.cancelIdleCallback?.bind(window),
|
||||
performance: window.performance,
|
||||
Intl: window.Intl,
|
||||
Date: window.Date,
|
||||
};
|
||||
const result = SinonFakeTimers.install(params);
|
||||
result.builtin = builtin;
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export class Highlight {
|
|||
if (this._rafRequest)
|
||||
cancelAnimationFrame(this._rafRequest);
|
||||
this.updateHighlight(this._injectedScript.querySelectorAll(selector, this._injectedScript.document.documentElement), { tooltipText: asLocator(this._language, stringifySelector(selector)) });
|
||||
this._rafRequest = requestAnimationFrame(() => this.runHighlightOnRaf(selector));
|
||||
this._rafRequest = this._injectedScript.builtinRequestAnimationFrame(() => this.runHighlightOnRaf(selector));
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
|
|
|
|||
|
|
@ -124,6 +124,18 @@ export class InjectedScript {
|
|||
(this.window as any).__injectedScript = this;
|
||||
}
|
||||
|
||||
builtinSetTimeout(callback: Function, timeout: number) {
|
||||
if (this.window.__pwFakeTimers?.builtin)
|
||||
return this.window.__pwFakeTimers.builtin.setTimeout(callback, timeout);
|
||||
return setTimeout(callback, timeout);
|
||||
}
|
||||
|
||||
builtinRequestAnimationFrame(callback: FrameRequestCallback) {
|
||||
if (this.window.__pwFakeTimers?.builtin)
|
||||
return this.window.__pwFakeTimers.builtin.requestAnimationFrame(callback);
|
||||
return requestAnimationFrame(callback);
|
||||
}
|
||||
|
||||
eval(expression: string): any {
|
||||
return this.window.eval(expression);
|
||||
}
|
||||
|
|
@ -427,7 +439,7 @@ export class InjectedScript {
|
|||
observer.observe(element);
|
||||
// Firefox doesn't call IntersectionObserver callback unless
|
||||
// there are rafs.
|
||||
requestAnimationFrame(() => {});
|
||||
this.builtinRequestAnimationFrame(() => {});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -536,12 +548,12 @@ export class InjectedScript {
|
|||
if (success !== continuePolling)
|
||||
fulfill(success);
|
||||
else
|
||||
requestAnimationFrame(raf);
|
||||
this.builtinRequestAnimationFrame(raf);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
requestAnimationFrame(raf);
|
||||
this.builtinRequestAnimationFrame(raf);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -1510,3 +1522,14 @@ function deepEquals(a: any, b: any): boolean {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__pwFakeTimers?: {
|
||||
builtin: {
|
||||
setTimeout: Window['setTimeout'],
|
||||
requestAnimationFrame: Window['requestAnimationFrame'],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -881,7 +881,7 @@ class Overlay {
|
|||
flashToolSucceeded(tool: 'assertingVisibility' | 'assertingValue') {
|
||||
const element = tool === 'assertingVisibility' ? this._assertVisibilityToggle : this._assertValuesToggle;
|
||||
element.classList.add('succeeded');
|
||||
setTimeout(() => element.classList.remove('succeeded'), 2000);
|
||||
this._recorder.injectedScript.builtinSetTimeout(() => element.classList.remove('succeeded'), 2000);
|
||||
}
|
||||
|
||||
private _hideOverlay() {
|
||||
|
|
@ -1312,7 +1312,7 @@ interface Embedder {
|
|||
export class PollingRecorder implements RecorderDelegate {
|
||||
private _recorder: Recorder;
|
||||
private _embedder: Embedder;
|
||||
private _pollRecorderModeTimer: NodeJS.Timeout | undefined;
|
||||
private _pollRecorderModeTimer: number | undefined;
|
||||
|
||||
constructor(injectedScript: InjectedScript) {
|
||||
this._recorder = new Recorder(injectedScript);
|
||||
|
|
@ -1333,7 +1333,7 @@ export class PollingRecorder implements RecorderDelegate {
|
|||
clearTimeout(this._pollRecorderModeTimer);
|
||||
const state = await this._embedder.__pw_recorderState().catch(() => {});
|
||||
if (!state) {
|
||||
this._pollRecorderModeTimer = setTimeout(() => this._pollRecorderMode(), pollPeriod);
|
||||
this._pollRecorderModeTimer = this._recorder.injectedScript.builtinSetTimeout(() => this._pollRecorderMode(), pollPeriod);
|
||||
return;
|
||||
}
|
||||
const win = this._recorder.document.defaultView!;
|
||||
|
|
@ -1343,7 +1343,7 @@ export class PollingRecorder implements RecorderDelegate {
|
|||
state.actionPoint = undefined;
|
||||
}
|
||||
this._recorder.setUIState(state, this);
|
||||
this._pollRecorderModeTimer = setTimeout(() => this._pollRecorderMode(), pollPeriod);
|
||||
this._pollRecorderModeTimer = this._recorder.injectedScript.builtinSetTimeout(() => this._pollRecorderMode(), pollPeriod);
|
||||
}
|
||||
|
||||
async performAction(action: actions.Action) {
|
||||
|
|
|
|||
|
|
@ -17,17 +17,20 @@
|
|||
import { serializeAsCallArgument, parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
|
||||
|
||||
export class UtilityScript {
|
||||
constructor(isUnderTest: boolean) {
|
||||
if (isUnderTest)
|
||||
this._setBuiltins();
|
||||
}
|
||||
|
||||
serializeAsCallArgument = serializeAsCallArgument;
|
||||
parseEvaluationResultValue = parseEvaluationResultValue;
|
||||
|
||||
evaluate(isFunction: boolean | undefined, returnByValue: boolean, exposeUtilityScript: boolean | undefined, expression: string, argCount: number, ...argsAndHandles: any[]) {
|
||||
evaluate(isFunction: boolean | undefined, returnByValue: boolean, expression: string, argCount: number, ...argsAndHandles: any[]) {
|
||||
const args = argsAndHandles.slice(0, argCount);
|
||||
const handles = argsAndHandles.slice(argCount);
|
||||
const parameters = [];
|
||||
for (let i = 0; i < args.length; i++)
|
||||
parameters[i] = this.parseEvaluationResultValue(args[i], handles);
|
||||
if (exposeUtilityScript)
|
||||
parameters.unshift(this);
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
let result = globalThis.eval(expression);
|
||||
|
|
@ -71,4 +74,47 @@ export class UtilityScript {
|
|||
}
|
||||
return safeJson(value);
|
||||
}
|
||||
|
||||
private _setBuiltins() {
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const window = (globalThis as any);
|
||||
window.builtinSetTimeout = (callback: Function, timeout: number) => {
|
||||
if (window.__pwFakeTimers?.builtin)
|
||||
return window.__pwFakeTimers.builtin.setTimeout(callback, timeout);
|
||||
return setTimeout(callback, timeout);
|
||||
};
|
||||
|
||||
window.builtinClearTimeout = (id: number) => {
|
||||
if (window.__pwFakeTimers?.builtin)
|
||||
return window.__pwFakeTimers.builtin.clearTimeout(id);
|
||||
return clearTimeout(id);
|
||||
};
|
||||
|
||||
window.builtinSetInterval = (callback: Function, timeout: number) => {
|
||||
if (window.__pwFakeTimers?.builtin)
|
||||
return window.__pwFakeTimers.builtin.setInterval(callback, timeout);
|
||||
return setInterval(callback, timeout);
|
||||
};
|
||||
|
||||
window.builtinClearInterval = (id: number) => {
|
||||
if (window.__pwFakeTimers?.builtin)
|
||||
return window.__pwFakeTimers.builtin.clearInterval(id);
|
||||
return clearInterval(id);
|
||||
};
|
||||
|
||||
window.builtinRequestAnimationFrame = (callback: FrameRequestCallback) => {
|
||||
if (window.__pwFakeTimers?.builtin)
|
||||
return window.__pwFakeTimers.builtin.requestAnimationFrame(callback);
|
||||
return requestAnimationFrame(callback);
|
||||
};
|
||||
|
||||
window.builtinCancelAnimationFrame = (id: number) => {
|
||||
if (window.__pwFakeTimers?.builtin)
|
||||
return window.__pwFakeTimers.builtin.cancelAnimationFrame(id);
|
||||
return cancelAnimationFrame(id);
|
||||
};
|
||||
|
||||
window.builtinDate = window.__pwFakeTimers?.builtin.Date || Date;
|
||||
window.builtinPerformance = window.__pwFakeTimers?.builtin.performance || performance;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { serializeAsCallArgument } from './isomorphic/utilityScriptSerializers';
|
|||
import type { UtilityScript } from './injected/utilityScript';
|
||||
import { SdkObject } from './instrumentation';
|
||||
import { LongStandingScope } from '../utils/manualPromise';
|
||||
import { isUnderTest } from '../utils';
|
||||
|
||||
export type ObjectId = string;
|
||||
export type RemoteObject = {
|
||||
|
|
@ -118,7 +119,7 @@ export class ExecutionContext extends SdkObject {
|
|||
(() => {
|
||||
const module = {};
|
||||
${utilityScriptSource.source}
|
||||
return new (module.exports.UtilityScript())();
|
||||
return new (module.exports.UtilityScript())(${isUnderTest()});
|
||||
})();`;
|
||||
this._utilityScriptPromise = this._raceAgainstContextDestroyed(this._delegate.rawEvaluateHandle(source).then(objectId => new JSHandle(this, 'object', 'UtilityScript', objectId)));
|
||||
}
|
||||
|
|
@ -257,7 +258,7 @@ export async function evaluate(context: ExecutionContext, returnByValue: boolean
|
|||
return evaluateExpression(context, String(pageFunction), { returnByValue, isFunction: typeof pageFunction === 'function' }, ...args);
|
||||
}
|
||||
|
||||
export async function evaluateExpression(context: ExecutionContext, expression: string, options: { returnByValue?: boolean, isFunction?: boolean, exposeUtilityScript?: boolean }, ...args: any[]): Promise<any> {
|
||||
export async function evaluateExpression(context: ExecutionContext, expression: string, options: { returnByValue?: boolean, isFunction?: boolean }, ...args: any[]): Promise<any> {
|
||||
const utilityScript = await context.utilityScript();
|
||||
expression = normalizeEvaluationExpression(expression, options.isFunction);
|
||||
const handles: (Promise<JSHandle>)[] = [];
|
||||
|
|
@ -290,7 +291,7 @@ export async function evaluateExpression(context: ExecutionContext, expression:
|
|||
}
|
||||
|
||||
// See UtilityScript for arguments.
|
||||
const utilityScriptValues = [options.isFunction, options.returnByValue, options.exposeUtilityScript, expression, args.length, ...args];
|
||||
const utilityScriptValues = [options.isFunction, options.returnByValue, expression, args.length, ...args];
|
||||
|
||||
const script = `(utilityScript, ...args) => utilityScript.evaluate(...args)`;
|
||||
try {
|
||||
|
|
|
|||
3
packages/playwright-core/types/types.d.ts
vendored
3
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -17274,8 +17274,7 @@ export interface Clock {
|
|||
|
||||
/**
|
||||
* An array with names of global methods and APIs to fake. For instance, `await page.clock.install({ toFake:
|
||||
* ['setTimeout'] })` will fake only `setTimeout()`. By default, `setTimeout`, `clearTimeout`, `setInterval`,
|
||||
* `clearInterval` and `Date` are faked.
|
||||
* ['setTimeout'] })` will fake only `setTimeout()`. By default, all the methods are faked.
|
||||
*/
|
||||
toFake?: Array<"setTimeout"|"clearTimeout"|"setInterval"|"clearInterval"|"Date"|"requestAnimationFrame"|"cancelAnimationFrame"|"requestIdleCallback"|"cancelIdleCallback"|"performance">;
|
||||
}): Promise<void>;
|
||||
|
|
|
|||
|
|
@ -2612,12 +2612,10 @@ export type FrameDispatchEventResult = void;
|
|||
export type FrameEvaluateExpressionParams = {
|
||||
expression: string,
|
||||
isFunction?: boolean,
|
||||
exposeUtilityScript?: boolean,
|
||||
arg: SerializedArgument,
|
||||
};
|
||||
export type FrameEvaluateExpressionOptions = {
|
||||
isFunction?: boolean,
|
||||
exposeUtilityScript?: boolean,
|
||||
};
|
||||
export type FrameEvaluateExpressionResult = {
|
||||
value: SerializedValue,
|
||||
|
|
|
|||
|
|
@ -1922,7 +1922,6 @@ Frame:
|
|||
parameters:
|
||||
expression: string
|
||||
isFunction: boolean?
|
||||
exposeUtilityScript: boolean?
|
||||
arg: SerializedArgument
|
||||
returns:
|
||||
value: SerializedValue
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
};
|
||||
|
||||
if (interstitial.classList.contains('timeout'))
|
||||
setTimeout(closeInterstitial, 3000);
|
||||
builtinSetTimeout(closeInterstitial, 3000);
|
||||
else
|
||||
closeInterstitial();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -41,3 +41,16 @@ export function step<This extends Object, Args extends any[], Return>(
|
|||
}
|
||||
return replacementMethod;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
builtinSetTimeout: WindowOrWorkerGlobalScope['setTimeout'],
|
||||
builtinClearTimeout: WindowOrWorkerGlobalScope['setTimeout'],
|
||||
builtinSetInterval: WindowOrWorkerGlobalScope['setInterval'],
|
||||
builtinClearInterval: WindowOrWorkerGlobalScope['clearInterval'],
|
||||
builtinRequestAnimationFrame: AnimationFrameProvider['requestAnimationFrame'],
|
||||
builtinCancelAnimationFrame: AnimationFrameProvider['cancelAnimationFrame'],
|
||||
builtinPerformance: WindowOrWorkerGlobalScope['performance'],
|
||||
builtinDate: typeof Date,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -391,6 +391,7 @@ it('should(not) block third party cookies', async ({ context, page, server, brow
|
|||
|
||||
it('should not block third party SameSite=None cookies', async ({ contextFactory, httpsServer, browserName }) => {
|
||||
it.skip(browserName === 'webkit', 'No third party cookies in WebKit');
|
||||
it.skip(!!process.env.PW_FREEZE_TIME);
|
||||
const context = await contextFactory({
|
||||
ignoreHTTPSErrors: true,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ test('console event should work in popup 2', async ({ page, browserName }) => {
|
|||
const [, message, popup] = await Promise.all([
|
||||
page.evaluate(async () => {
|
||||
const win = window.open('javascript:console.log("hello")')!;
|
||||
await new Promise(f => setTimeout(f, 0));
|
||||
await new Promise(f => window.builtinSetTimeout(f, 0));
|
||||
win.close();
|
||||
}),
|
||||
page.context().waitForEvent('console', msg => msg.type() === 'log'),
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ it('should reject all promises when browser is closed', async ({ browserType })
|
|||
const page = await (await browser.newContext()).newPage();
|
||||
let error: Error | undefined;
|
||||
const neverResolves = page.evaluate(() => new Promise(r => {})).catch(e => error = e);
|
||||
await page.evaluate(() => new Promise(f => setTimeout(f, 0)));
|
||||
await page.evaluate(() => new Promise(f => window.builtinSetTimeout(f, 0)));
|
||||
await browser.close();
|
||||
await neverResolves;
|
||||
// WebKit under task-set -c 1 is giving browser, rest are giving target.
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ it('should work with a recently loaded stylesheet', async function({ page, serve
|
|||
link.href = url;
|
||||
document.head.appendChild(link);
|
||||
await new Promise(x => link.onload = x);
|
||||
await new Promise(f => requestAnimationFrame(f));
|
||||
await new Promise(f => window.builtinRequestAnimationFrame(f));
|
||||
}, server.PREFIX + '/csscoverage/stylesheet1.css');
|
||||
const coverage = await page.coverage.stopCSSCoverage();
|
||||
expect(coverage.length).toBe(1);
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ it('should ignore eval() scripts by default', async function({ page, server }) {
|
|||
});
|
||||
|
||||
it('shouldn\'t ignore eval() scripts if reportAnonymousScripts is true', async function({ page, server }) {
|
||||
it.skip(!!process.env.PW_FREEZE_TIME);
|
||||
await page.coverage.startJSCoverage({ reportAnonymousScripts: true });
|
||||
await page.goto(server.PREFIX + '/jscoverage/eval.html');
|
||||
const coverage = await page.coverage.stopJSCoverage();
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ it('should(not) block third party cookies', async ({ page, server, allowsThirdPa
|
|||
|
||||
it('should not block third party SameSite=None cookies', async ({ httpsServer, browserName, browser }) => {
|
||||
it.skip(browserName === 'webkit', 'No third party cookies in WebKit');
|
||||
it.skip(!!process.env.PW_FREEZE_TIME);
|
||||
const page = await browser.newPage({
|
||||
ignoreHTTPSErrors: true,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -136,9 +136,9 @@ it('should use viewport size from window features', async function({ browser, se
|
|||
page.evaluate(async () => {
|
||||
const win = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=600,height=300,top=0,left=0');
|
||||
await new Promise<void>(resolve => {
|
||||
const interval = setInterval(() => {
|
||||
const interval = window.builtinSetInterval(() => {
|
||||
if (win.innerWidth === 600 && win.innerHeight === 300) {
|
||||
clearInterval(interval);
|
||||
window.builtinClearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, 10);
|
||||
|
|
@ -281,8 +281,8 @@ async function waitForRafs(page: Page, count: number): Promise<void> {
|
|||
if (!count)
|
||||
resolve();
|
||||
else
|
||||
requestAnimationFrame(onRaf);
|
||||
window.builtinRequestAnimationFrame(onRaf);
|
||||
};
|
||||
requestAnimationFrame(onRaf);
|
||||
window.builtinRequestAnimationFrame(onRaf);
|
||||
}), count);
|
||||
}
|
||||
|
|
@ -44,9 +44,9 @@ test.beforeAll(async function recordTrace({ browser, browserName, browserType, s
|
|||
console.error('Error');
|
||||
return new Promise(f => {
|
||||
// Generate exception.
|
||||
setTimeout(() => {
|
||||
window.builtinSetTimeout(() => {
|
||||
// And then resolve.
|
||||
setTimeout(() => f('return ' + a), 0);
|
||||
window.builtinSetTimeout(() => f('return ' + a), 0);
|
||||
throw new Error('Unhandled exception');
|
||||
}, 0);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -426,7 +426,7 @@ for (const params of [
|
|||
// Make sure we have a chance to paint.
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
await page.setContent('<body style="box-sizing: border-box; width: 100%; height: 100%; margin:0; background: red; border: 50px solid blue"></body>');
|
||||
await page.evaluate(() => new Promise(requestAnimationFrame));
|
||||
await page.evaluate(() => new Promise(window.builtinRequestAnimationFrame));
|
||||
}
|
||||
await context.tracing.stop({ path: testInfo.outputPath('trace.zip') });
|
||||
|
||||
|
|
@ -709,7 +709,7 @@ test('should not flush console events', async ({ context, page, mode }, testInfo
|
|||
});
|
||||
|
||||
await page.evaluate(() => {
|
||||
setTimeout(() => {
|
||||
window.builtinSetTimeout(() => {
|
||||
for (let i = 0; i < 100; ++i)
|
||||
console.log('hello ' + i);
|
||||
}, 10);
|
||||
|
|
@ -749,7 +749,7 @@ test('should flush console events on tracing stop', async ({ context, page }, te
|
|||
});
|
||||
});
|
||||
await page.evaluate(() => {
|
||||
setTimeout(() => {
|
||||
window.builtinSetTimeout(() => {
|
||||
for (let i = 0; i < 100; ++i)
|
||||
console.log('hello ' + i);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -829,8 +829,8 @@ async function waitForRafs(page: Page, count: number): Promise<void> {
|
|||
if (!count)
|
||||
resolve();
|
||||
else
|
||||
requestAnimationFrame(onRaf);
|
||||
window.builtinRequestAnimationFrame(onRaf);
|
||||
};
|
||||
requestAnimationFrame(onRaf);
|
||||
window.builtinRequestAnimationFrame(onRaf);
|
||||
}), count);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { test as it, expect } from './pageTest';
|
||||
import { test as it, expect, rafraf } from './pageTest';
|
||||
import { verifyViewport } from '../config/utils';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
|
@ -207,8 +207,7 @@ it.describe('element screenshot', () => {
|
|||
done = true;
|
||||
return buffer;
|
||||
});
|
||||
for (let i = 0; i < 10; i++)
|
||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(f)));
|
||||
await rafraf(page, 10);
|
||||
expect(done).toBe(false);
|
||||
await elementHandle.evaluate(e => e.style.visibility = 'visible');
|
||||
const screenshot = await promise;
|
||||
|
|
@ -233,10 +232,8 @@ it.describe('element screenshot', () => {
|
|||
await page.setViewportSize({ width: 500, height: 500 });
|
||||
await page.goto(server.PREFIX + '/grid.html');
|
||||
const elementHandle = await page.$('.box:nth-of-type(3)');
|
||||
await elementHandle.evaluate(e => {
|
||||
e.classList.add('animation');
|
||||
return new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f)));
|
||||
});
|
||||
await elementHandle.evaluate(e => e.classList.add('animation'));
|
||||
await rafraf(page);
|
||||
const screenshot = await elementHandle.screenshot();
|
||||
expect(screenshot).toMatchSnapshot('screenshot-element-bounding-box.png');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ async function testWaiting(page, after) {
|
|||
const div = await page.$('div');
|
||||
let done = false;
|
||||
const promise = div.scrollIntoViewIfNeeded().then(() => done = true);
|
||||
await page.evaluate(() => new Promise(f => setTimeout(f, 1000)));
|
||||
await page.waitForTimeout(1000);
|
||||
expect(done).toBe(false);
|
||||
await div.evaluate(after);
|
||||
await promise;
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ it('should wait for visible', async ({ page, server }) => {
|
|||
await textarea.evaluate(e => e.style.display = 'none');
|
||||
let done = false;
|
||||
const promise = textarea.selectText({ timeout: 3000 }).then(() => done = true);
|
||||
await page.evaluate(() => new Promise(f => setTimeout(f, 1000)));
|
||||
await page.waitForTimeout(1000);
|
||||
expect(done).toBe(false);
|
||||
await textarea.evaluate(e => e.style.display = 'block');
|
||||
await promise;
|
||||
|
|
|
|||
|
|
@ -15,12 +15,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { test as it, expect } from './pageTest';
|
||||
import type { Page } from '@playwright/test';
|
||||
import { test as it, expect, rafraf } from './pageTest';
|
||||
|
||||
async function giveItAChanceToResolve(page) {
|
||||
for (let i = 0; i < 5; i++)
|
||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
||||
}
|
||||
const giveItAChanceToResolve = (page: Page) => rafraf(page, 5);
|
||||
|
||||
it('should wait for visible', async ({ page }) => {
|
||||
await page.setContent(`<div style='display:none'>content</div>`);
|
||||
|
|
@ -124,7 +122,7 @@ it('should wait for stable position', async ({ page, server, browserName, platfo
|
|||
button.style.marginLeft = '20000px';
|
||||
});
|
||||
// rafraf for Firefox to kick in the animation.
|
||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
||||
await rafraf(page);
|
||||
let done = false;
|
||||
const promise = button.waitForElementState('stable').then(() => done = true);
|
||||
await giveItAChanceToResolve(page);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { test as it, expect } from './pageTest';
|
||||
import { test as it, expect, rafraf } from './pageTest';
|
||||
|
||||
it('should timeout waiting for stable position', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
|
|
@ -25,7 +25,7 @@ it('should timeout waiting for stable position', async ({ page, server }) => {
|
|||
button.style.marginLeft = '200px';
|
||||
});
|
||||
// rafraf for Firefox to kick in the animation.
|
||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
||||
await rafraf(page);
|
||||
const error = await button.click({ timeout: 3000 }).catch(e => e);
|
||||
expect(error.message).toContain('elementHandle.click: Timeout 3000ms exceeded.');
|
||||
expect(error.message).toContain('waiting for element to be visible, enabled and stable');
|
||||
|
|
|
|||
|
|
@ -15,13 +15,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { test as it, expect } from './pageTest';
|
||||
import { test as it, expect, rafraf } from './pageTest';
|
||||
import { attachFrame, detachFrame } from '../config/utils';
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
async function giveItAChanceToClick(page) {
|
||||
for (let i = 0; i < 5; i++)
|
||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
||||
}
|
||||
const giveItAChanceToClick = (page: Page) => rafraf(page, 5);
|
||||
|
||||
it('should click the button @smoke', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
|
|
@ -456,7 +454,7 @@ it('should wait for stable position', async ({ page, server }) => {
|
|||
document.body.style.margin = '0';
|
||||
});
|
||||
// rafraf for Firefox to kick in the animation.
|
||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
||||
await rafraf(page);
|
||||
await page.click('button');
|
||||
expect(await page.evaluate(() => window['result'])).toBe('Clicked');
|
||||
expect(await page.evaluate('pageX')).toBe(300);
|
||||
|
|
@ -1072,7 +1070,7 @@ it('ensure events are dispatched in the individual tasks', async ({ page, browse
|
|||
function onClick(name) {
|
||||
console.log(`click ${name}`);
|
||||
|
||||
setTimeout(function() {
|
||||
window.builtinSetTimeout(function() {
|
||||
console.log(`timeout ${name}`);
|
||||
}, 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
import { test, expect } from './pageTest';
|
||||
|
||||
test.skip(!!process.env.PW_FREEZE_TIME);
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
stub: (param?: any) => void
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ import { test as it, expect } from './pageTest';
|
|||
it.skip(({ isWebView2 }) => isWebView2, 'Page.close() is not supported in WebView2');
|
||||
|
||||
it('should close page with active dialog', async ({ page }) => {
|
||||
await page.setContent(`<button onclick="setTimeout(() => alert(1))">alert</button>`);
|
||||
await page.evaluate('"trigger builtinSetTimeout"');
|
||||
await page.setContent(`<button onclick="builtinSetTimeout(() => alert(1))">alert</button>`);
|
||||
void page.click('button').catch(() => {});
|
||||
await page.waitForEvent('dialog');
|
||||
await page.close();
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ it('should dismiss the confirm prompt', async ({ page }) => {
|
|||
it('should be able to close context with open alert', async ({ page }) => {
|
||||
const alertPromise = page.waitForEvent('dialog');
|
||||
await page.evaluate(() => {
|
||||
setTimeout(() => alert('hello'), 0);
|
||||
window.builtinSetTimeout(() => alert('hello'), 0);
|
||||
});
|
||||
await alertPromise;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ it('should dispatch click when node is added in shadow dom', async ({ page, serv
|
|||
div.attachShadow({ mode: 'open' });
|
||||
document.body.appendChild(div);
|
||||
});
|
||||
await page.evaluate(() => new Promise(f => setTimeout(f, 100)));
|
||||
await page.waitForTimeout(100);
|
||||
await page.evaluate(() => {
|
||||
const span = document.createElement('span');
|
||||
span.textContent = 'Hello from shadow';
|
||||
|
|
|
|||
|
|
@ -357,7 +357,7 @@ it('should report event.buttons', async ({ page, browserName }) => {
|
|||
function onEvent(event) {
|
||||
logs.push({ type: event.type, buttons: event.buttons });
|
||||
}
|
||||
await new Promise(requestAnimationFrame);
|
||||
await new Promise(window.builtinRequestAnimationFrame);
|
||||
return logs;
|
||||
});
|
||||
await page.mouse.move(20, 20);
|
||||
|
|
|
|||
|
|
@ -349,10 +349,10 @@ it('should properly serialize null fields', async ({ page }) => {
|
|||
|
||||
it('should properly serialize PerformanceMeasure object', async ({ page }) => {
|
||||
expect(await page.evaluate(() => {
|
||||
window.performance.mark('start');
|
||||
window.performance.mark('end');
|
||||
window.performance.measure('my-measure', 'start', 'end');
|
||||
return performance.getEntriesByType('measure');
|
||||
window.builtinPerformance.mark('start');
|
||||
window.builtinPerformance.mark('end');
|
||||
window.builtinPerformance.measure('my-measure', 'start', 'end');
|
||||
return window.builtinPerformance.getEntriesByType('measure');
|
||||
})).toEqual([{
|
||||
duration: expect.any(Number),
|
||||
entryType: 'measure',
|
||||
|
|
@ -362,6 +362,8 @@ it('should properly serialize PerformanceMeasure object', async ({ page }) => {
|
|||
});
|
||||
|
||||
it('should properly serialize window.performance object', async ({ page }) => {
|
||||
it.skip(!!process.env.PW_FREEZE_TIME);
|
||||
|
||||
expect(await page.evaluate(() => performance)).toEqual({
|
||||
'navigation': {
|
||||
'redirectCount': 0,
|
||||
|
|
@ -760,16 +762,6 @@ it('should work with overridden URL/Date/RegExp', async ({ page, server }) => {
|
|||
}
|
||||
});
|
||||
|
||||
it('should expose utilityScript', async ({ page }) => {
|
||||
const result = await (page.mainFrame() as any)._evaluateExposeUtilityScript((utilityScript, { a }) => {
|
||||
return { utils: 'parseEvaluationResultValue' in utilityScript, a };
|
||||
}, { a: 42 });
|
||||
expect(result).toEqual({
|
||||
a: 42,
|
||||
utils: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with Array.from/map', async ({ page }) => {
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28520' });
|
||||
expect(await page.evaluate(() => {
|
||||
|
|
|
|||
|
|
@ -97,9 +97,9 @@ it('should format the message correctly with time/timeLog/timeEnd', async ({ pag
|
|||
page.on('console', msg => messages.push(msg));
|
||||
await page.evaluate(async () => {
|
||||
console.time('foo time');
|
||||
await new Promise(x => setTimeout(x, 100));
|
||||
await new Promise(x => window.builtinSetTimeout(x, 100));
|
||||
console.timeLog('foo time');
|
||||
await new Promise(x => setTimeout(x, 100));
|
||||
await new Promise(x => window.builtinSetTimeout(x, 100));
|
||||
console.timeEnd('foo time');
|
||||
});
|
||||
expect(messages.length).toBe(2);
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ it('should contain the Error.name property', async ({ page }) => {
|
|||
const [error] = await Promise.all([
|
||||
page.waitForEvent('pageerror'),
|
||||
page.evaluate(() => {
|
||||
setTimeout(() => {
|
||||
window.builtinSetTimeout(() => {
|
||||
const error = new Error('my-message');
|
||||
error.name = 'my-name';
|
||||
throw error;
|
||||
|
|
@ -85,7 +85,7 @@ it('should support an empty Error.name property', async ({ page }) => {
|
|||
const [error] = await Promise.all([
|
||||
page.waitForEvent('pageerror'),
|
||||
page.evaluate(() => {
|
||||
setTimeout(() => {
|
||||
window.builtinSetTimeout(() => {
|
||||
const error = new Error('my-message');
|
||||
error.name = '';
|
||||
throw error;
|
||||
|
|
@ -106,7 +106,9 @@ it('should handle odd values', async ({ page }) => {
|
|||
for (const [value, message] of cases) {
|
||||
const [error] = await Promise.all([
|
||||
page.waitForEvent('pageerror'),
|
||||
page.evaluate(value => setTimeout(() => { throw value; }, 0), value),
|
||||
page.evaluate(value => {
|
||||
window.builtinSetTimeout(() => { throw value; }, 0);
|
||||
}, value),
|
||||
]);
|
||||
expect(error.message).toBe(message);
|
||||
}
|
||||
|
|
@ -115,7 +117,9 @@ it('should handle odd values', async ({ page }) => {
|
|||
it('should handle object', async ({ page, browserName }) => {
|
||||
const [error] = await Promise.all([
|
||||
page.waitForEvent('pageerror'),
|
||||
page.evaluate(() => setTimeout(() => { throw {}; }, 0)),
|
||||
page.evaluate(() => {
|
||||
window.builtinSetTimeout(() => { throw {}; }, 0);
|
||||
}),
|
||||
]);
|
||||
expect(error.message).toBe(browserName === 'chromium' ? 'Object' : '[object Object]');
|
||||
});
|
||||
|
|
@ -123,7 +127,9 @@ it('should handle object', async ({ page, browserName }) => {
|
|||
it('should handle window', async ({ page, browserName }) => {
|
||||
const [error] = await Promise.all([
|
||||
page.waitForEvent('pageerror'),
|
||||
page.evaluate(() => setTimeout(() => { throw window; }, 0)),
|
||||
page.evaluate(() => {
|
||||
window.builtinSetTimeout(() => { throw window; }, 0);
|
||||
}),
|
||||
]);
|
||||
expect(error.message).toBe(browserName === 'chromium' ? 'Window' : '[object Window]');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ it('should not result in unhandled rejection', async ({ page, isAndroid, isWebVi
|
|||
await page.close();
|
||||
});
|
||||
await page.evaluate(() => {
|
||||
setTimeout(() => (window as any).foo(), 0);
|
||||
window.builtinSetTimeout(() => (window as any).foo(), 0);
|
||||
return undefined;
|
||||
});
|
||||
await closedPromise;
|
||||
|
|
|
|||
|
|
@ -15,12 +15,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { test as it, expect } from './pageTest';
|
||||
import type { Page } from '@playwright/test';
|
||||
import { test as it, expect, rafraf } from './pageTest';
|
||||
|
||||
async function giveItAChanceToFill(page) {
|
||||
for (let i = 0; i < 5; i++)
|
||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
||||
}
|
||||
const giveItAChanceToFill = (page: Page) => rafraf(page, 5);
|
||||
|
||||
it('should fill textarea @smoke', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
||||
|
|
|
|||
|
|
@ -481,6 +481,7 @@ it('js redirect overrides url bar navigation ', async ({ page, server, browserNa
|
|||
|
||||
it('should succeed on url bar navigation when there is pending navigation', async ({ page, server, browserName }) => {
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/21574' });
|
||||
it.skip(!!process.env.PW_FREEZE_TIME);
|
||||
server.setRoute('/a', (req, res) => {
|
||||
res.writeHead(200, { 'content-type': 'text/html' });
|
||||
res.end(`
|
||||
|
|
@ -509,7 +510,7 @@ it('should succeed on url bar navigation when there is pending navigation', asyn
|
|||
events.push('finished c');
|
||||
});
|
||||
await page.goto(server.PREFIX + '/a');
|
||||
await new Promise(f => setTimeout(f, 1000));
|
||||
await page.waitForTimeout(1000);
|
||||
const error = await page.goto(server.PREFIX + '/b').then(r => null, e => e);
|
||||
const expectEvents = ['started c', 'started b', 'finished c', 'finished b'];
|
||||
await expect(() => expect(events).toEqual(expectEvents)).toPass({ timeout: 5000 });
|
||||
|
|
@ -753,6 +754,7 @@ it('should properly wait for load', async ({ page, server, browserName }) => {
|
|||
|
||||
it('should not resolve goto upon window.stop()', async ({ browserName, page, server }) => {
|
||||
it.fixme(browserName === 'firefox', 'load/domcontentloaded events are flaky');
|
||||
it.skip(!!process.env.PW_FREEZE_TIME);
|
||||
|
||||
let response;
|
||||
server.setRoute('/module.js', (req, res) => {
|
||||
|
|
@ -795,6 +797,7 @@ it('should return when navigation is committed if commit is specified', async ({
|
|||
});
|
||||
|
||||
it('should wait for load when iframe attaches and detaches', async ({ page, server }) => {
|
||||
it.skip(!!process.env.PW_FREEZE_TIME);
|
||||
server.setRoute('/empty.html', (req, res) => {
|
||||
res.writeHead(200, { 'content-type': 'text/html' });
|
||||
res.end(`
|
||||
|
|
|
|||
|
|
@ -245,6 +245,7 @@ it('page.goForward during renderer-initiated navigation', async ({ page, server
|
|||
|
||||
it('regression test for issue 20791', async ({ page, server }) => {
|
||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/20791' });
|
||||
it.skip(!!process.env.PW_FREEZE_TIME);
|
||||
server.setRoute('/iframe.html', (req, res) => {
|
||||
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
||||
// iframe access parent frame to log a value from it.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { test as it, expect } from './pageTest';
|
||||
import { test as it, expect, rafraf } from './pageTest';
|
||||
|
||||
function dimensions() {
|
||||
const rect = document.querySelector('textarea').getBoundingClientRect();
|
||||
|
|
@ -150,7 +150,7 @@ it('should select the text with mouse', async ({ page, server }) => {
|
|||
const text = 'This is the text that we are going to try to select. Let\'s see how it goes.';
|
||||
await page.keyboard.type(text);
|
||||
// Firefox needs an extra frame here after typing or it will fail to set the scrollTop
|
||||
await page.evaluate(() => new Promise(requestAnimationFrame));
|
||||
await rafraf(page);
|
||||
await page.evaluate(() => document.querySelector('textarea').scrollTop = 0);
|
||||
const { x, y } = await page.evaluate(dimensions);
|
||||
await page.mouse.move(x + 2, y + 2);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
|
||||
import os from 'os';
|
||||
import { test as it, expect } from './pageTest';
|
||||
import { test as it, expect, rafraf } from './pageTest';
|
||||
import { verifyViewport, attachFrame } from '../config/utils';
|
||||
import type { Route } from 'playwright-core';
|
||||
import path from 'path';
|
||||
|
|
@ -589,14 +589,6 @@ it.describe('page screenshot', () => {
|
|||
});
|
||||
});
|
||||
|
||||
async function rafraf(page) {
|
||||
// Do a double raf since single raf does not
|
||||
// actually guarantee a new animation frame.
|
||||
await page.evaluate(() => new Promise(x => {
|
||||
requestAnimationFrame(() => requestAnimationFrame(x));
|
||||
}));
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
animation?: Animation;
|
||||
|
|
@ -732,9 +724,9 @@ it.describe('page screenshot animations', () => {
|
|||
const div = page.locator('div');
|
||||
await div.evaluate(el => {
|
||||
el.addEventListener('transitionend', () => {
|
||||
const time = Date.now();
|
||||
const time = window.builtinDate.now();
|
||||
// Block main thread for 200ms, emulating heavy layout.
|
||||
while (Date.now() - time < 200) {}
|
||||
while (window.builtinDate.now() - time < 200) {}
|
||||
const h1 = document.createElement('h1');
|
||||
h1.textContent = 'woof-woof';
|
||||
document.body.append(h1);
|
||||
|
|
|
|||
|
|
@ -15,12 +15,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { test as it, expect } from './pageTest';
|
||||
import type { Page } from '@playwright/test';
|
||||
import { test as it, expect, rafraf } from './pageTest';
|
||||
|
||||
async function giveItAChanceToResolve(page) {
|
||||
for (let i = 0; i < 5; i++)
|
||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
||||
}
|
||||
const giveItAChanceToResolve = (page: Page) => rafraf(page, 5);
|
||||
|
||||
it('should select single option @smoke', async ({ page, server }) => {
|
||||
await page.goto(server.PREFIX + '/input/select.html');
|
||||
|
|
|
|||
|
|
@ -399,7 +399,7 @@ it('should prioritize exact timeout over default timeout', async ({ page, playwr
|
|||
it('should work with no timeout', async ({ page, server }) => {
|
||||
const [chooser] = await Promise.all([
|
||||
page.waitForEvent('filechooser', { timeout: 0 }),
|
||||
page.evaluate(() => setTimeout(() => {
|
||||
page.evaluate(() => window.builtinSetTimeout(() => {
|
||||
const el = document.createElement('input');
|
||||
el.type = 'file';
|
||||
el.click();
|
||||
|
|
|
|||
|
|
@ -44,10 +44,10 @@ it('should poll on interval', async ({ page, server }) => {
|
|||
const polling = 100;
|
||||
const timeDelta = await page.waitForFunction(() => {
|
||||
if (!window['__startTime']) {
|
||||
window['__startTime'] = Date.now();
|
||||
window['__startTime'] = window.builtinDate.now();
|
||||
return false;
|
||||
}
|
||||
return Date.now() - window['__startTime'];
|
||||
return window.builtinDate.now() - window['__startTime'];
|
||||
}, {}, { polling });
|
||||
expect(await timeDelta.jsonValue()).not.toBeLessThan(polling);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ it('should fail when frame detaches', async ({ page, server }) => {
|
|||
frame.waitForNavigation().catch(e => e),
|
||||
page.$eval('iframe', frame => { frame.contentWindow.location.href = '/one-style.html'; }),
|
||||
// Make sure policy checks pass and navigation actually begins before removing the frame to avoid other errors
|
||||
server.waitForRequest('/one-style.css').then(() => page.$eval('iframe', frame => setTimeout(() => frame.remove(), 0)))
|
||||
server.waitForRequest('/one-style.css').then(() => page.$eval('iframe', frame => window.builtinSetTimeout(() => frame.remove(), 0)))
|
||||
]);
|
||||
expect(error.message).toContain('waiting for navigation until "load"');
|
||||
expect(error.message).toContain('frame was detached');
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ it('should work with no timeout', async ({ page, server }) => {
|
|||
await page.goto(server.EMPTY_PAGE);
|
||||
const [request] = await Promise.all([
|
||||
page.waitForRequest(server.PREFIX + '/digits/2.png', { timeout: 0 }),
|
||||
page.evaluate(() => setTimeout(() => {
|
||||
page.evaluate(() => window.builtinSetTimeout(() => {
|
||||
void fetch('/digits/1.png');
|
||||
void fetch('/digits/2.png');
|
||||
void fetch('/digits/3.png');
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ it('should work with no timeout', async ({ page, server }) => {
|
|||
await page.goto(server.EMPTY_PAGE);
|
||||
const [response] = await Promise.all([
|
||||
page.waitForResponse(server.PREFIX + '/digits/2.png', { timeout: 0 }),
|
||||
page.evaluate(() => setTimeout(() => {
|
||||
page.evaluate(() => window.builtinSetTimeout(() => {
|
||||
void fetch('/digits/1.png');
|
||||
void fetch('/digits/2.png');
|
||||
void fetch('/digits/3.png');
|
||||
|
|
|
|||
|
|
@ -16,12 +16,11 @@
|
|||
*/
|
||||
|
||||
import type { Frame } from '@playwright/test';
|
||||
import { test as it, expect } from './pageTest';
|
||||
import { test as it, expect, rafraf } from './pageTest';
|
||||
import { attachFrame, detachFrame } from '../config/utils';
|
||||
|
||||
async function giveItTimeToLog(frame: Frame) {
|
||||
await frame.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
||||
await frame.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
||||
await rafraf(frame, 2);
|
||||
}
|
||||
|
||||
const addElement = (tag: string) => document.body.appendChild(document.createElement(tag));
|
||||
|
|
@ -189,7 +188,7 @@ it('should resolve promise when node is added in shadow dom', async ({ page, ser
|
|||
div.attachShadow({ mode: 'open' });
|
||||
document.body.appendChild(div);
|
||||
});
|
||||
await page.evaluate(() => new Promise(f => setTimeout(f, 100)));
|
||||
await page.waitForTimeout(100);
|
||||
await page.evaluate(() => {
|
||||
const span = document.createElement('span');
|
||||
span.textContent = 'Hello from shadow';
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { TestType } from '@playwright/test';
|
||||
import type { Frame, Page, TestType } from '@playwright/test';
|
||||
import type { PlatformWorkerFixtures } from '../config/platformFixtures';
|
||||
import type { TestModeTestFixtures, TestModeWorkerFixtures, TestModeWorkerOptions } from '../config/testModeFixtures';
|
||||
import { androidTest } from '../android/androidTest';
|
||||
|
|
@ -35,3 +35,11 @@ if (process.env.PWPAGE_IMPL === 'webview2')
|
|||
impl = webView2Test;
|
||||
|
||||
export const test = impl;
|
||||
|
||||
export async function rafraf(target: Page | Frame, count = 1) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
await target.evaluate(async () => {
|
||||
await new Promise(f => window.builtinRequestAnimationFrame(() => window.builtinRequestAnimationFrame(f)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,12 +16,9 @@
|
|||
*/
|
||||
|
||||
import type { Page } from '@playwright/test';
|
||||
import { test as it, expect } from './pageTest';
|
||||
import { test as it, expect, rafraf } from './pageTest';
|
||||
|
||||
async function giveItAChanceToResolve(page: Page) {
|
||||
for (let i = 0; i < 5; i++)
|
||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
||||
}
|
||||
const giveItAChanceToResolve = (page: Page) => rafraf(page, 5);
|
||||
|
||||
it('element state checks should work as expected for label with zero-sized input', async ({ page, server }) => {
|
||||
await page.setContent(`
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
import type { Page } from 'playwright-core';
|
||||
import { test as it, expect } from './pageTest';
|
||||
import { test as it, expect, rafraf } from './pageTest';
|
||||
|
||||
it.skip(({ isAndroid }) => {
|
||||
return isAndroid;
|
||||
|
|
@ -209,8 +209,7 @@ it('should work when the event is canceled', async ({ page }) => {
|
|||
document.querySelector('div').addEventListener('wheel', e => e.preventDefault());
|
||||
});
|
||||
// Give wheel listener a chance to propagate through all the layers in Firefox.
|
||||
for (let i = 0; i < 10; i++)
|
||||
await page.evaluate(() => new Promise(x => requestAnimationFrame(() => requestAnimationFrame(x))));
|
||||
await rafraf(page, 10);
|
||||
await page.mouse.wheel(0, 100);
|
||||
await expectEvent(page, {
|
||||
deltaX: 0,
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ test('should format console messages in page', async ({ runUITest }, testInfo) =
|
|||
await expect(link).toHaveCSS('text-decoration', 'none solid rgb(0, 0, 255)');
|
||||
});
|
||||
|
||||
test('should stream console messages live', async ({ runUITest }, testInfo) => {
|
||||
test('should stream console messages live', async ({ runUITest }) => {
|
||||
const { page } = await runUITest({
|
||||
'a.spec.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
|
@ -162,7 +162,7 @@ test('should stream console messages live', async ({ runUITest }, testInfo) => {
|
|||
await page.setContent('<button>Click me</button>');
|
||||
const button = page.getByRole('button', { name: 'Click me' });
|
||||
await button.evaluate(node => node.addEventListener('click', () => {
|
||||
setTimeout(() => { console.log('I was clicked'); }, 1000);
|
||||
builtinSetTimeout(() => { console.log('I was clicked'); }, 1000);
|
||||
}));
|
||||
console.log('I was logged');
|
||||
await button.click();
|
||||
|
|
|
|||
Loading…
Reference in a new issue