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">>>
|
- `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()`.
|
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
|
### option: Clock.install.loopLimit
|
||||||
* since: v1.45
|
* 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 response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
|
||||||
const context = BrowserContext.from(response.context);
|
const context = BrowserContext.from(response.context);
|
||||||
await this._browserType._didCreateContext(context, contextOptions, this._options, options.logger || this._logger);
|
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;
|
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> {
|
async _evaluateExposeUtilityScript<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<R> {
|
||||||
assertMaxArguments(arguments.length, 2);
|
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);
|
return parseResult(result.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1429,7 +1429,6 @@ scheme.FrameDispatchEventResult = tOptional(tObject({}));
|
||||||
scheme.FrameEvaluateExpressionParams = tObject({
|
scheme.FrameEvaluateExpressionParams = tObject({
|
||||||
expression: tString,
|
expression: tString,
|
||||||
isFunction: tOptional(tBoolean),
|
isFunction: tOptional(tBoolean),
|
||||||
exposeUtilityScript: tOptional(tBoolean),
|
|
||||||
arg: tType('SerializedArgument'),
|
arg: tType('SerializedArgument'),
|
||||||
});
|
});
|
||||||
scheme.FrameEvaluateExpressionResult = tObject({
|
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> {
|
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> {
|
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);
|
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);
|
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);
|
return js.evaluateExpression(this, expression, { ...options, returnByValue: false }, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -745,13 +745,13 @@ export class Frame extends SdkObject {
|
||||||
return this._context('utility');
|
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 context = await this._context(options.world ?? 'main');
|
||||||
const value = await context.evaluateExpression(expression, options, arg);
|
const value = await context.evaluateExpression(expression, options, arg);
|
||||||
return value;
|
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 context = await this._context(options.world ?? 'main');
|
||||||
const value = await context.evaluateExpressionHandle(expression, options, arg);
|
const value = await context.evaluateExpressionHandle(expression, options, arg);
|
||||||
return value;
|
return value;
|
||||||
|
|
@ -1513,9 +1513,9 @@ export class Frame extends SdkObject {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof polling !== 'number')
|
if (typeof polling !== 'number')
|
||||||
requestAnimationFrame(next);
|
injected.builtinRequestAnimationFrame(next);
|
||||||
else
|
else
|
||||||
setTimeout(next, polling);
|
injected.builtinSetTimeout(next, polling);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,22 @@ import SinonFakeTimers from '../../third_party/fake-timers-src';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
|
|
||||||
export function install(params: channels.BrowserContextClockInstallOptions) {
|
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)
|
if (this._rafRequest)
|
||||||
cancelAnimationFrame(this._rafRequest);
|
cancelAnimationFrame(this._rafRequest);
|
||||||
this.updateHighlight(this._injectedScript.querySelectorAll(selector, this._injectedScript.document.documentElement), { tooltipText: asLocator(this._language, stringifySelector(selector)) });
|
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() {
|
uninstall() {
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,18 @@ export class InjectedScript {
|
||||||
(this.window as any).__injectedScript = this;
|
(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 {
|
eval(expression: string): any {
|
||||||
return this.window.eval(expression);
|
return this.window.eval(expression);
|
||||||
}
|
}
|
||||||
|
|
@ -427,7 +439,7 @@ export class InjectedScript {
|
||||||
observer.observe(element);
|
observer.observe(element);
|
||||||
// Firefox doesn't call IntersectionObserver callback unless
|
// Firefox doesn't call IntersectionObserver callback unless
|
||||||
// there are rafs.
|
// there are rafs.
|
||||||
requestAnimationFrame(() => {});
|
this.builtinRequestAnimationFrame(() => {});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -536,12 +548,12 @@ export class InjectedScript {
|
||||||
if (success !== continuePolling)
|
if (success !== continuePolling)
|
||||||
fulfill(success);
|
fulfill(success);
|
||||||
else
|
else
|
||||||
requestAnimationFrame(raf);
|
this.builtinRequestAnimationFrame(raf);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
requestAnimationFrame(raf);
|
this.builtinRequestAnimationFrame(raf);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -1510,3 +1522,14 @@ function deepEquals(a: any, b: any): boolean {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__pwFakeTimers?: {
|
||||||
|
builtin: {
|
||||||
|
setTimeout: Window['setTimeout'],
|
||||||
|
requestAnimationFrame: Window['requestAnimationFrame'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -881,7 +881,7 @@ class Overlay {
|
||||||
flashToolSucceeded(tool: 'assertingVisibility' | 'assertingValue') {
|
flashToolSucceeded(tool: 'assertingVisibility' | 'assertingValue') {
|
||||||
const element = tool === 'assertingVisibility' ? this._assertVisibilityToggle : this._assertValuesToggle;
|
const element = tool === 'assertingVisibility' ? this._assertVisibilityToggle : this._assertValuesToggle;
|
||||||
element.classList.add('succeeded');
|
element.classList.add('succeeded');
|
||||||
setTimeout(() => element.classList.remove('succeeded'), 2000);
|
this._recorder.injectedScript.builtinSetTimeout(() => element.classList.remove('succeeded'), 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _hideOverlay() {
|
private _hideOverlay() {
|
||||||
|
|
@ -1312,7 +1312,7 @@ interface Embedder {
|
||||||
export class PollingRecorder implements RecorderDelegate {
|
export class PollingRecorder implements RecorderDelegate {
|
||||||
private _recorder: Recorder;
|
private _recorder: Recorder;
|
||||||
private _embedder: Embedder;
|
private _embedder: Embedder;
|
||||||
private _pollRecorderModeTimer: NodeJS.Timeout | undefined;
|
private _pollRecorderModeTimer: number | undefined;
|
||||||
|
|
||||||
constructor(injectedScript: InjectedScript) {
|
constructor(injectedScript: InjectedScript) {
|
||||||
this._recorder = new Recorder(injectedScript);
|
this._recorder = new Recorder(injectedScript);
|
||||||
|
|
@ -1333,7 +1333,7 @@ export class PollingRecorder implements RecorderDelegate {
|
||||||
clearTimeout(this._pollRecorderModeTimer);
|
clearTimeout(this._pollRecorderModeTimer);
|
||||||
const state = await this._embedder.__pw_recorderState().catch(() => {});
|
const state = await this._embedder.__pw_recorderState().catch(() => {});
|
||||||
if (!state) {
|
if (!state) {
|
||||||
this._pollRecorderModeTimer = setTimeout(() => this._pollRecorderMode(), pollPeriod);
|
this._pollRecorderModeTimer = this._recorder.injectedScript.builtinSetTimeout(() => this._pollRecorderMode(), pollPeriod);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const win = this._recorder.document.defaultView!;
|
const win = this._recorder.document.defaultView!;
|
||||||
|
|
@ -1343,7 +1343,7 @@ export class PollingRecorder implements RecorderDelegate {
|
||||||
state.actionPoint = undefined;
|
state.actionPoint = undefined;
|
||||||
}
|
}
|
||||||
this._recorder.setUIState(state, this);
|
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) {
|
async performAction(action: actions.Action) {
|
||||||
|
|
|
||||||
|
|
@ -17,17 +17,20 @@
|
||||||
import { serializeAsCallArgument, parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
|
import { serializeAsCallArgument, parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
|
||||||
|
|
||||||
export class UtilityScript {
|
export class UtilityScript {
|
||||||
|
constructor(isUnderTest: boolean) {
|
||||||
|
if (isUnderTest)
|
||||||
|
this._setBuiltins();
|
||||||
|
}
|
||||||
|
|
||||||
serializeAsCallArgument = serializeAsCallArgument;
|
serializeAsCallArgument = serializeAsCallArgument;
|
||||||
parseEvaluationResultValue = parseEvaluationResultValue;
|
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 args = argsAndHandles.slice(0, argCount);
|
||||||
const handles = argsAndHandles.slice(argCount);
|
const handles = argsAndHandles.slice(argCount);
|
||||||
const parameters = [];
|
const parameters = [];
|
||||||
for (let i = 0; i < args.length; i++)
|
for (let i = 0; i < args.length; i++)
|
||||||
parameters[i] = this.parseEvaluationResultValue(args[i], handles);
|
parameters[i] = this.parseEvaluationResultValue(args[i], handles);
|
||||||
if (exposeUtilityScript)
|
|
||||||
parameters.unshift(this);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-globals
|
// eslint-disable-next-line no-restricted-globals
|
||||||
let result = globalThis.eval(expression);
|
let result = globalThis.eval(expression);
|
||||||
|
|
@ -71,4 +74,47 @@ export class UtilityScript {
|
||||||
}
|
}
|
||||||
return safeJson(value);
|
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 type { UtilityScript } from './injected/utilityScript';
|
||||||
import { SdkObject } from './instrumentation';
|
import { SdkObject } from './instrumentation';
|
||||||
import { LongStandingScope } from '../utils/manualPromise';
|
import { LongStandingScope } from '../utils/manualPromise';
|
||||||
|
import { isUnderTest } from '../utils';
|
||||||
|
|
||||||
export type ObjectId = string;
|
export type ObjectId = string;
|
||||||
export type RemoteObject = {
|
export type RemoteObject = {
|
||||||
|
|
@ -118,7 +119,7 @@ export class ExecutionContext extends SdkObject {
|
||||||
(() => {
|
(() => {
|
||||||
const module = {};
|
const module = {};
|
||||||
${utilityScriptSource.source}
|
${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)));
|
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);
|
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();
|
const utilityScript = await context.utilityScript();
|
||||||
expression = normalizeEvaluationExpression(expression, options.isFunction);
|
expression = normalizeEvaluationExpression(expression, options.isFunction);
|
||||||
const handles: (Promise<JSHandle>)[] = [];
|
const handles: (Promise<JSHandle>)[] = [];
|
||||||
|
|
@ -290,7 +291,7 @@ export async function evaluateExpression(context: ExecutionContext, expression:
|
||||||
}
|
}
|
||||||
|
|
||||||
// See UtilityScript for arguments.
|
// 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)`;
|
const script = `(utilityScript, ...args) => utilityScript.evaluate(...args)`;
|
||||||
try {
|
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:
|
* 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`,
|
* ['setTimeout'] })` will fake only `setTimeout()`. By default, all the methods are faked.
|
||||||
* `clearInterval` and `Date` are faked.
|
|
||||||
*/
|
*/
|
||||||
toFake?: Array<"setTimeout"|"clearTimeout"|"setInterval"|"clearInterval"|"Date"|"requestAnimationFrame"|"cancelAnimationFrame"|"requestIdleCallback"|"cancelIdleCallback"|"performance">;
|
toFake?: Array<"setTimeout"|"clearTimeout"|"setInterval"|"clearInterval"|"Date"|"requestAnimationFrame"|"cancelAnimationFrame"|"requestIdleCallback"|"cancelIdleCallback"|"performance">;
|
||||||
}): Promise<void>;
|
}): Promise<void>;
|
||||||
|
|
|
||||||
|
|
@ -2612,12 +2612,10 @@ export type FrameDispatchEventResult = void;
|
||||||
export type FrameEvaluateExpressionParams = {
|
export type FrameEvaluateExpressionParams = {
|
||||||
expression: string,
|
expression: string,
|
||||||
isFunction?: boolean,
|
isFunction?: boolean,
|
||||||
exposeUtilityScript?: boolean,
|
|
||||||
arg: SerializedArgument,
|
arg: SerializedArgument,
|
||||||
};
|
};
|
||||||
export type FrameEvaluateExpressionOptions = {
|
export type FrameEvaluateExpressionOptions = {
|
||||||
isFunction?: boolean,
|
isFunction?: boolean,
|
||||||
exposeUtilityScript?: boolean,
|
|
||||||
};
|
};
|
||||||
export type FrameEvaluateExpressionResult = {
|
export type FrameEvaluateExpressionResult = {
|
||||||
value: SerializedValue,
|
value: SerializedValue,
|
||||||
|
|
|
||||||
|
|
@ -1922,7 +1922,6 @@ Frame:
|
||||||
parameters:
|
parameters:
|
||||||
expression: string
|
expression: string
|
||||||
isFunction: boolean?
|
isFunction: boolean?
|
||||||
exposeUtilityScript: boolean?
|
|
||||||
arg: SerializedArgument
|
arg: SerializedArgument
|
||||||
returns:
|
returns:
|
||||||
value: SerializedValue
|
value: SerializedValue
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
if (interstitial.classList.contains('timeout'))
|
if (interstitial.classList.contains('timeout'))
|
||||||
setTimeout(closeInterstitial, 3000);
|
builtinSetTimeout(closeInterstitial, 3000);
|
||||||
else
|
else
|
||||||
closeInterstitial();
|
closeInterstitial();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -41,3 +41,16 @@ export function step<This extends Object, Args extends any[], Return>(
|
||||||
}
|
}
|
||||||
return replacementMethod;
|
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('should not block third party SameSite=None cookies', async ({ contextFactory, httpsServer, browserName }) => {
|
||||||
it.skip(browserName === 'webkit', 'No third party cookies in WebKit');
|
it.skip(browserName === 'webkit', 'No third party cookies in WebKit');
|
||||||
|
it.skip(!!process.env.PW_FREEZE_TIME);
|
||||||
const context = await contextFactory({
|
const context = await contextFactory({
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ test('console event should work in popup 2', async ({ page, browserName }) => {
|
||||||
const [, message, popup] = await Promise.all([
|
const [, message, popup] = await Promise.all([
|
||||||
page.evaluate(async () => {
|
page.evaluate(async () => {
|
||||||
const win = window.open('javascript:console.log("hello")')!;
|
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();
|
win.close();
|
||||||
}),
|
}),
|
||||||
page.context().waitForEvent('console', msg => msg.type() === 'log'),
|
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();
|
const page = await (await browser.newContext()).newPage();
|
||||||
let error: Error | undefined;
|
let error: Error | undefined;
|
||||||
const neverResolves = page.evaluate(() => new Promise(r => {})).catch(e => error = e);
|
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 browser.close();
|
||||||
await neverResolves;
|
await neverResolves;
|
||||||
// WebKit under task-set -c 1 is giving browser, rest are giving target.
|
// 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;
|
link.href = url;
|
||||||
document.head.appendChild(link);
|
document.head.appendChild(link);
|
||||||
await new Promise(x => link.onload = x);
|
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');
|
}, server.PREFIX + '/csscoverage/stylesheet1.css');
|
||||||
const coverage = await page.coverage.stopCSSCoverage();
|
const coverage = await page.coverage.stopCSSCoverage();
|
||||||
expect(coverage.length).toBe(1);
|
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('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.coverage.startJSCoverage({ reportAnonymousScripts: true });
|
||||||
await page.goto(server.PREFIX + '/jscoverage/eval.html');
|
await page.goto(server.PREFIX + '/jscoverage/eval.html');
|
||||||
const coverage = await page.coverage.stopJSCoverage();
|
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('should not block third party SameSite=None cookies', async ({ httpsServer, browserName, browser }) => {
|
||||||
it.skip(browserName === 'webkit', 'No third party cookies in WebKit');
|
it.skip(browserName === 'webkit', 'No third party cookies in WebKit');
|
||||||
|
it.skip(!!process.env.PW_FREEZE_TIME);
|
||||||
const page = await browser.newPage({
|
const page = await browser.newPage({
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -136,9 +136,9 @@ it('should use viewport size from window features', async function({ browser, se
|
||||||
page.evaluate(async () => {
|
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');
|
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 => {
|
await new Promise<void>(resolve => {
|
||||||
const interval = setInterval(() => {
|
const interval = window.builtinSetInterval(() => {
|
||||||
if (win.innerWidth === 600 && win.innerHeight === 300) {
|
if (win.innerWidth === 600 && win.innerHeight === 300) {
|
||||||
clearInterval(interval);
|
window.builtinClearInterval(interval);
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
}, 10);
|
}, 10);
|
||||||
|
|
@ -281,8 +281,8 @@ async function waitForRafs(page: Page, count: number): Promise<void> {
|
||||||
if (!count)
|
if (!count)
|
||||||
resolve();
|
resolve();
|
||||||
else
|
else
|
||||||
requestAnimationFrame(onRaf);
|
window.builtinRequestAnimationFrame(onRaf);
|
||||||
};
|
};
|
||||||
requestAnimationFrame(onRaf);
|
window.builtinRequestAnimationFrame(onRaf);
|
||||||
}), count);
|
}), count);
|
||||||
}
|
}
|
||||||
|
|
@ -44,9 +44,9 @@ test.beforeAll(async function recordTrace({ browser, browserName, browserType, s
|
||||||
console.error('Error');
|
console.error('Error');
|
||||||
return new Promise(f => {
|
return new Promise(f => {
|
||||||
// Generate exception.
|
// Generate exception.
|
||||||
setTimeout(() => {
|
window.builtinSetTimeout(() => {
|
||||||
// And then resolve.
|
// And then resolve.
|
||||||
setTimeout(() => f('return ' + a), 0);
|
window.builtinSetTimeout(() => f('return ' + a), 0);
|
||||||
throw new Error('Unhandled exception');
|
throw new Error('Unhandled exception');
|
||||||
}, 0);
|
}, 0);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -426,7 +426,7 @@ for (const params of [
|
||||||
// Make sure we have a chance to paint.
|
// Make sure we have a chance to paint.
|
||||||
for (let i = 0; i < 10; ++i) {
|
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.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') });
|
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(() => {
|
await page.evaluate(() => {
|
||||||
setTimeout(() => {
|
window.builtinSetTimeout(() => {
|
||||||
for (let i = 0; i < 100; ++i)
|
for (let i = 0; i < 100; ++i)
|
||||||
console.log('hello ' + i);
|
console.log('hello ' + i);
|
||||||
}, 10);
|
}, 10);
|
||||||
|
|
@ -749,7 +749,7 @@ test('should flush console events on tracing stop', async ({ context, page }, te
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
setTimeout(() => {
|
window.builtinSetTimeout(() => {
|
||||||
for (let i = 0; i < 100; ++i)
|
for (let i = 0; i < 100; ++i)
|
||||||
console.log('hello ' + i);
|
console.log('hello ' + i);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -829,8 +829,8 @@ async function waitForRafs(page: Page, count: number): Promise<void> {
|
||||||
if (!count)
|
if (!count)
|
||||||
resolve();
|
resolve();
|
||||||
else
|
else
|
||||||
requestAnimationFrame(onRaf);
|
window.builtinRequestAnimationFrame(onRaf);
|
||||||
};
|
};
|
||||||
requestAnimationFrame(onRaf);
|
window.builtinRequestAnimationFrame(onRaf);
|
||||||
}), count);
|
}), count);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* 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 { verifyViewport } from '../config/utils';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
|
@ -207,8 +207,7 @@ it.describe('element screenshot', () => {
|
||||||
done = true;
|
done = true;
|
||||||
return buffer;
|
return buffer;
|
||||||
});
|
});
|
||||||
for (let i = 0; i < 10; i++)
|
await rafraf(page, 10);
|
||||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(f)));
|
|
||||||
expect(done).toBe(false);
|
expect(done).toBe(false);
|
||||||
await elementHandle.evaluate(e => e.style.visibility = 'visible');
|
await elementHandle.evaluate(e => e.style.visibility = 'visible');
|
||||||
const screenshot = await promise;
|
const screenshot = await promise;
|
||||||
|
|
@ -233,10 +232,8 @@ it.describe('element screenshot', () => {
|
||||||
await page.setViewportSize({ width: 500, height: 500 });
|
await page.setViewportSize({ width: 500, height: 500 });
|
||||||
await page.goto(server.PREFIX + '/grid.html');
|
await page.goto(server.PREFIX + '/grid.html');
|
||||||
const elementHandle = await page.$('.box:nth-of-type(3)');
|
const elementHandle = await page.$('.box:nth-of-type(3)');
|
||||||
await elementHandle.evaluate(e => {
|
await elementHandle.evaluate(e => e.classList.add('animation'));
|
||||||
e.classList.add('animation');
|
await rafraf(page);
|
||||||
return new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f)));
|
|
||||||
});
|
|
||||||
const screenshot = await elementHandle.screenshot();
|
const screenshot = await elementHandle.screenshot();
|
||||||
expect(screenshot).toMatchSnapshot('screenshot-element-bounding-box.png');
|
expect(screenshot).toMatchSnapshot('screenshot-element-bounding-box.png');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ async function testWaiting(page, after) {
|
||||||
const div = await page.$('div');
|
const div = await page.$('div');
|
||||||
let done = false;
|
let done = false;
|
||||||
const promise = div.scrollIntoViewIfNeeded().then(() => done = true);
|
const promise = div.scrollIntoViewIfNeeded().then(() => done = true);
|
||||||
await page.evaluate(() => new Promise(f => setTimeout(f, 1000)));
|
await page.waitForTimeout(1000);
|
||||||
expect(done).toBe(false);
|
expect(done).toBe(false);
|
||||||
await div.evaluate(after);
|
await div.evaluate(after);
|
||||||
await promise;
|
await promise;
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ it('should wait for visible', async ({ page, server }) => {
|
||||||
await textarea.evaluate(e => e.style.display = 'none');
|
await textarea.evaluate(e => e.style.display = 'none');
|
||||||
let done = false;
|
let done = false;
|
||||||
const promise = textarea.selectText({ timeout: 3000 }).then(() => done = true);
|
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);
|
expect(done).toBe(false);
|
||||||
await textarea.evaluate(e => e.style.display = 'block');
|
await textarea.evaluate(e => e.style.display = 'block');
|
||||||
await promise;
|
await promise;
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,10 @@
|
||||||
* limitations under the License.
|
* 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) {
|
const giveItAChanceToResolve = (page: Page) => rafraf(page, 5);
|
||||||
for (let i = 0; i < 5; i++)
|
|
||||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should wait for visible', async ({ page }) => {
|
it('should wait for visible', async ({ page }) => {
|
||||||
await page.setContent(`<div style='display:none'>content</div>`);
|
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';
|
button.style.marginLeft = '20000px';
|
||||||
});
|
});
|
||||||
// rafraf for Firefox to kick in the animation.
|
// rafraf for Firefox to kick in the animation.
|
||||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
await rafraf(page);
|
||||||
let done = false;
|
let done = false;
|
||||||
const promise = button.waitForElementState('stable').then(() => done = true);
|
const promise = button.waitForElementState('stable').then(() => done = true);
|
||||||
await giveItAChanceToResolve(page);
|
await giveItAChanceToResolve(page);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* 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 }) => {
|
it('should timeout waiting for stable position', async ({ page, server }) => {
|
||||||
await page.goto(server.PREFIX + '/input/button.html');
|
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';
|
button.style.marginLeft = '200px';
|
||||||
});
|
});
|
||||||
// rafraf for Firefox to kick in the animation.
|
// 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);
|
const error = await button.click({ timeout: 3000 }).catch(e => e);
|
||||||
expect(error.message).toContain('elementHandle.click: Timeout 3000ms exceeded.');
|
expect(error.message).toContain('elementHandle.click: Timeout 3000ms exceeded.');
|
||||||
expect(error.message).toContain('waiting for element to be visible, enabled and stable');
|
expect(error.message).toContain('waiting for element to be visible, enabled and stable');
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,11 @@
|
||||||
* limitations under the License.
|
* 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 { attachFrame, detachFrame } from '../config/utils';
|
||||||
|
import type { Page } from '@playwright/test';
|
||||||
|
|
||||||
async function giveItAChanceToClick(page) {
|
const giveItAChanceToClick = (page: Page) => rafraf(page, 5);
|
||||||
for (let i = 0; i < 5; i++)
|
|
||||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should click the button @smoke', async ({ page, server }) => {
|
it('should click the button @smoke', async ({ page, server }) => {
|
||||||
await page.goto(server.PREFIX + '/input/button.html');
|
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';
|
document.body.style.margin = '0';
|
||||||
});
|
});
|
||||||
// rafraf for Firefox to kick in the animation.
|
// rafraf for Firefox to kick in the animation.
|
||||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
await rafraf(page);
|
||||||
await page.click('button');
|
await page.click('button');
|
||||||
expect(await page.evaluate(() => window['result'])).toBe('Clicked');
|
expect(await page.evaluate(() => window['result'])).toBe('Clicked');
|
||||||
expect(await page.evaluate('pageX')).toBe(300);
|
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) {
|
function onClick(name) {
|
||||||
console.log(`click ${name}`);
|
console.log(`click ${name}`);
|
||||||
|
|
||||||
setTimeout(function() {
|
window.builtinSetTimeout(function() {
|
||||||
console.log(`timeout ${name}`);
|
console.log(`timeout ${name}`);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
import { test, expect } from './pageTest';
|
import { test, expect } from './pageTest';
|
||||||
|
|
||||||
|
test.skip(!!process.env.PW_FREEZE_TIME);
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
stub: (param?: any) => void
|
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.skip(({ isWebView2 }) => isWebView2, 'Page.close() is not supported in WebView2');
|
||||||
|
|
||||||
it('should close page with active dialog', async ({ page }) => {
|
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(() => {});
|
void page.click('button').catch(() => {});
|
||||||
await page.waitForEvent('dialog');
|
await page.waitForEvent('dialog');
|
||||||
await page.close();
|
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 }) => {
|
it('should be able to close context with open alert', async ({ page }) => {
|
||||||
const alertPromise = page.waitForEvent('dialog');
|
const alertPromise = page.waitForEvent('dialog');
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
setTimeout(() => alert('hello'), 0);
|
window.builtinSetTimeout(() => alert('hello'), 0);
|
||||||
});
|
});
|
||||||
await alertPromise;
|
await alertPromise;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ it('should dispatch click when node is added in shadow dom', async ({ page, serv
|
||||||
div.attachShadow({ mode: 'open' });
|
div.attachShadow({ mode: 'open' });
|
||||||
document.body.appendChild(div);
|
document.body.appendChild(div);
|
||||||
});
|
});
|
||||||
await page.evaluate(() => new Promise(f => setTimeout(f, 100)));
|
await page.waitForTimeout(100);
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
const span = document.createElement('span');
|
const span = document.createElement('span');
|
||||||
span.textContent = 'Hello from shadow';
|
span.textContent = 'Hello from shadow';
|
||||||
|
|
|
||||||
|
|
@ -357,7 +357,7 @@ it('should report event.buttons', async ({ page, browserName }) => {
|
||||||
function onEvent(event) {
|
function onEvent(event) {
|
||||||
logs.push({ type: event.type, buttons: event.buttons });
|
logs.push({ type: event.type, buttons: event.buttons });
|
||||||
}
|
}
|
||||||
await new Promise(requestAnimationFrame);
|
await new Promise(window.builtinRequestAnimationFrame);
|
||||||
return logs;
|
return logs;
|
||||||
});
|
});
|
||||||
await page.mouse.move(20, 20);
|
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 }) => {
|
it('should properly serialize PerformanceMeasure object', async ({ page }) => {
|
||||||
expect(await page.evaluate(() => {
|
expect(await page.evaluate(() => {
|
||||||
window.performance.mark('start');
|
window.builtinPerformance.mark('start');
|
||||||
window.performance.mark('end');
|
window.builtinPerformance.mark('end');
|
||||||
window.performance.measure('my-measure', 'start', 'end');
|
window.builtinPerformance.measure('my-measure', 'start', 'end');
|
||||||
return performance.getEntriesByType('measure');
|
return window.builtinPerformance.getEntriesByType('measure');
|
||||||
})).toEqual([{
|
})).toEqual([{
|
||||||
duration: expect.any(Number),
|
duration: expect.any(Number),
|
||||||
entryType: 'measure',
|
entryType: 'measure',
|
||||||
|
|
@ -362,6 +362,8 @@ it('should properly serialize PerformanceMeasure object', async ({ page }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should properly serialize window.performance 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({
|
expect(await page.evaluate(() => performance)).toEqual({
|
||||||
'navigation': {
|
'navigation': {
|
||||||
'redirectCount': 0,
|
'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('should work with Array.from/map', async ({ page }) => {
|
||||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28520' });
|
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/28520' });
|
||||||
expect(await page.evaluate(() => {
|
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));
|
page.on('console', msg => messages.push(msg));
|
||||||
await page.evaluate(async () => {
|
await page.evaluate(async () => {
|
||||||
console.time('foo time');
|
console.time('foo time');
|
||||||
await new Promise(x => setTimeout(x, 100));
|
await new Promise(x => window.builtinSetTimeout(x, 100));
|
||||||
console.timeLog('foo time');
|
console.timeLog('foo time');
|
||||||
await new Promise(x => setTimeout(x, 100));
|
await new Promise(x => window.builtinSetTimeout(x, 100));
|
||||||
console.timeEnd('foo time');
|
console.timeEnd('foo time');
|
||||||
});
|
});
|
||||||
expect(messages.length).toBe(2);
|
expect(messages.length).toBe(2);
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ it('should contain the Error.name property', async ({ page }) => {
|
||||||
const [error] = await Promise.all([
|
const [error] = await Promise.all([
|
||||||
page.waitForEvent('pageerror'),
|
page.waitForEvent('pageerror'),
|
||||||
page.evaluate(() => {
|
page.evaluate(() => {
|
||||||
setTimeout(() => {
|
window.builtinSetTimeout(() => {
|
||||||
const error = new Error('my-message');
|
const error = new Error('my-message');
|
||||||
error.name = 'my-name';
|
error.name = 'my-name';
|
||||||
throw error;
|
throw error;
|
||||||
|
|
@ -85,7 +85,7 @@ it('should support an empty Error.name property', async ({ page }) => {
|
||||||
const [error] = await Promise.all([
|
const [error] = await Promise.all([
|
||||||
page.waitForEvent('pageerror'),
|
page.waitForEvent('pageerror'),
|
||||||
page.evaluate(() => {
|
page.evaluate(() => {
|
||||||
setTimeout(() => {
|
window.builtinSetTimeout(() => {
|
||||||
const error = new Error('my-message');
|
const error = new Error('my-message');
|
||||||
error.name = '';
|
error.name = '';
|
||||||
throw error;
|
throw error;
|
||||||
|
|
@ -106,7 +106,9 @@ it('should handle odd values', async ({ page }) => {
|
||||||
for (const [value, message] of cases) {
|
for (const [value, message] of cases) {
|
||||||
const [error] = await Promise.all([
|
const [error] = await Promise.all([
|
||||||
page.waitForEvent('pageerror'),
|
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);
|
expect(error.message).toBe(message);
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +117,9 @@ it('should handle odd values', async ({ page }) => {
|
||||||
it('should handle object', async ({ page, browserName }) => {
|
it('should handle object', async ({ page, browserName }) => {
|
||||||
const [error] = await Promise.all([
|
const [error] = await Promise.all([
|
||||||
page.waitForEvent('pageerror'),
|
page.waitForEvent('pageerror'),
|
||||||
page.evaluate(() => setTimeout(() => { throw {}; }, 0)),
|
page.evaluate(() => {
|
||||||
|
window.builtinSetTimeout(() => { throw {}; }, 0);
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
expect(error.message).toBe(browserName === 'chromium' ? 'Object' : '[object Object]');
|
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 }) => {
|
it('should handle window', async ({ page, browserName }) => {
|
||||||
const [error] = await Promise.all([
|
const [error] = await Promise.all([
|
||||||
page.waitForEvent('pageerror'),
|
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]');
|
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.close();
|
||||||
});
|
});
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
setTimeout(() => (window as any).foo(), 0);
|
window.builtinSetTimeout(() => (window as any).foo(), 0);
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
await closedPromise;
|
await closedPromise;
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,10 @@
|
||||||
* limitations under the License.
|
* 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) {
|
const giveItAChanceToFill = (page: Page) => rafraf(page, 5);
|
||||||
for (let i = 0; i < 5; i++)
|
|
||||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should fill textarea @smoke', async ({ page, server }) => {
|
it('should fill textarea @smoke', async ({ page, server }) => {
|
||||||
await page.goto(server.PREFIX + '/input/textarea.html');
|
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('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.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) => {
|
server.setRoute('/a', (req, res) => {
|
||||||
res.writeHead(200, { 'content-type': 'text/html' });
|
res.writeHead(200, { 'content-type': 'text/html' });
|
||||||
res.end(`
|
res.end(`
|
||||||
|
|
@ -509,7 +510,7 @@ it('should succeed on url bar navigation when there is pending navigation', asyn
|
||||||
events.push('finished c');
|
events.push('finished c');
|
||||||
});
|
});
|
||||||
await page.goto(server.PREFIX + '/a');
|
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 error = await page.goto(server.PREFIX + '/b').then(r => null, e => e);
|
||||||
const expectEvents = ['started c', 'started b', 'finished c', 'finished b'];
|
const expectEvents = ['started c', 'started b', 'finished c', 'finished b'];
|
||||||
await expect(() => expect(events).toEqual(expectEvents)).toPass({ timeout: 5000 });
|
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('should not resolve goto upon window.stop()', async ({ browserName, page, server }) => {
|
||||||
it.fixme(browserName === 'firefox', 'load/domcontentloaded events are flaky');
|
it.fixme(browserName === 'firefox', 'load/domcontentloaded events are flaky');
|
||||||
|
it.skip(!!process.env.PW_FREEZE_TIME);
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
server.setRoute('/module.js', (req, res) => {
|
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('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) => {
|
server.setRoute('/empty.html', (req, res) => {
|
||||||
res.writeHead(200, { 'content-type': 'text/html' });
|
res.writeHead(200, { 'content-type': 'text/html' });
|
||||||
res.end(`
|
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('regression test for issue 20791', async ({ page, server }) => {
|
||||||
it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/20791' });
|
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) => {
|
server.setRoute('/iframe.html', (req, res) => {
|
||||||
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
||||||
// iframe access parent frame to log a value from it.
|
// iframe access parent frame to log a value from it.
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { test as it, expect } from './pageTest';
|
import { test as it, expect, rafraf } from './pageTest';
|
||||||
|
|
||||||
function dimensions() {
|
function dimensions() {
|
||||||
const rect = document.querySelector('textarea').getBoundingClientRect();
|
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.';
|
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);
|
await page.keyboard.type(text);
|
||||||
// Firefox needs an extra frame here after typing or it will fail to set the scrollTop
|
// 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);
|
await page.evaluate(() => document.querySelector('textarea').scrollTop = 0);
|
||||||
const { x, y } = await page.evaluate(dimensions);
|
const { x, y } = await page.evaluate(dimensions);
|
||||||
await page.mouse.move(x + 2, y + 2);
|
await page.mouse.move(x + 2, y + 2);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import os from 'os';
|
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 { verifyViewport, attachFrame } from '../config/utils';
|
||||||
import type { Route } from 'playwright-core';
|
import type { Route } from 'playwright-core';
|
||||||
import path from 'path';
|
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 {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
animation?: Animation;
|
animation?: Animation;
|
||||||
|
|
@ -732,9 +724,9 @@ it.describe('page screenshot animations', () => {
|
||||||
const div = page.locator('div');
|
const div = page.locator('div');
|
||||||
await div.evaluate(el => {
|
await div.evaluate(el => {
|
||||||
el.addEventListener('transitionend', () => {
|
el.addEventListener('transitionend', () => {
|
||||||
const time = Date.now();
|
const time = window.builtinDate.now();
|
||||||
// Block main thread for 200ms, emulating heavy layout.
|
// Block main thread for 200ms, emulating heavy layout.
|
||||||
while (Date.now() - time < 200) {}
|
while (window.builtinDate.now() - time < 200) {}
|
||||||
const h1 = document.createElement('h1');
|
const h1 = document.createElement('h1');
|
||||||
h1.textContent = 'woof-woof';
|
h1.textContent = 'woof-woof';
|
||||||
document.body.append(h1);
|
document.body.append(h1);
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,10 @@
|
||||||
* limitations under the License.
|
* 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) {
|
const giveItAChanceToResolve = (page: Page) => rafraf(page, 5);
|
||||||
for (let i = 0; i < 5; i++)
|
|
||||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should select single option @smoke', async ({ page, server }) => {
|
it('should select single option @smoke', async ({ page, server }) => {
|
||||||
await page.goto(server.PREFIX + '/input/select.html');
|
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 }) => {
|
it('should work with no timeout', async ({ page, server }) => {
|
||||||
const [chooser] = await Promise.all([
|
const [chooser] = await Promise.all([
|
||||||
page.waitForEvent('filechooser', { timeout: 0 }),
|
page.waitForEvent('filechooser', { timeout: 0 }),
|
||||||
page.evaluate(() => setTimeout(() => {
|
page.evaluate(() => window.builtinSetTimeout(() => {
|
||||||
const el = document.createElement('input');
|
const el = document.createElement('input');
|
||||||
el.type = 'file';
|
el.type = 'file';
|
||||||
el.click();
|
el.click();
|
||||||
|
|
|
||||||
|
|
@ -44,10 +44,10 @@ it('should poll on interval', async ({ page, server }) => {
|
||||||
const polling = 100;
|
const polling = 100;
|
||||||
const timeDelta = await page.waitForFunction(() => {
|
const timeDelta = await page.waitForFunction(() => {
|
||||||
if (!window['__startTime']) {
|
if (!window['__startTime']) {
|
||||||
window['__startTime'] = Date.now();
|
window['__startTime'] = window.builtinDate.now();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return Date.now() - window['__startTime'];
|
return window.builtinDate.now() - window['__startTime'];
|
||||||
}, {}, { polling });
|
}, {}, { polling });
|
||||||
expect(await timeDelta.jsonValue()).not.toBeLessThan(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),
|
frame.waitForNavigation().catch(e => e),
|
||||||
page.$eval('iframe', frame => { frame.contentWindow.location.href = '/one-style.html'; }),
|
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
|
// 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('waiting for navigation until "load"');
|
||||||
expect(error.message).toContain('frame was detached');
|
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);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
const [request] = await Promise.all([
|
const [request] = await Promise.all([
|
||||||
page.waitForRequest(server.PREFIX + '/digits/2.png', { timeout: 0 }),
|
page.waitForRequest(server.PREFIX + '/digits/2.png', { timeout: 0 }),
|
||||||
page.evaluate(() => setTimeout(() => {
|
page.evaluate(() => window.builtinSetTimeout(() => {
|
||||||
void fetch('/digits/1.png');
|
void fetch('/digits/1.png');
|
||||||
void fetch('/digits/2.png');
|
void fetch('/digits/2.png');
|
||||||
void fetch('/digits/3.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);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
const [response] = await Promise.all([
|
const [response] = await Promise.all([
|
||||||
page.waitForResponse(server.PREFIX + '/digits/2.png', { timeout: 0 }),
|
page.waitForResponse(server.PREFIX + '/digits/2.png', { timeout: 0 }),
|
||||||
page.evaluate(() => setTimeout(() => {
|
page.evaluate(() => window.builtinSetTimeout(() => {
|
||||||
void fetch('/digits/1.png');
|
void fetch('/digits/1.png');
|
||||||
void fetch('/digits/2.png');
|
void fetch('/digits/2.png');
|
||||||
void fetch('/digits/3.png');
|
void fetch('/digits/3.png');
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Frame } from '@playwright/test';
|
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';
|
import { attachFrame, detachFrame } from '../config/utils';
|
||||||
|
|
||||||
async function giveItTimeToLog(frame: Frame) {
|
async function giveItTimeToLog(frame: Frame) {
|
||||||
await frame.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
await rafraf(frame, 2);
|
||||||
await frame.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const addElement = (tag: string) => document.body.appendChild(document.createElement(tag));
|
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' });
|
div.attachShadow({ mode: 'open' });
|
||||||
document.body.appendChild(div);
|
document.body.appendChild(div);
|
||||||
});
|
});
|
||||||
await page.evaluate(() => new Promise(f => setTimeout(f, 100)));
|
await page.waitForTimeout(100);
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
const span = document.createElement('span');
|
const span = document.createElement('span');
|
||||||
span.textContent = 'Hello from shadow';
|
span.textContent = 'Hello from shadow';
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* 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 { PlatformWorkerFixtures } from '../config/platformFixtures';
|
||||||
import type { TestModeTestFixtures, TestModeWorkerFixtures, TestModeWorkerOptions } from '../config/testModeFixtures';
|
import type { TestModeTestFixtures, TestModeWorkerFixtures, TestModeWorkerOptions } from '../config/testModeFixtures';
|
||||||
import { androidTest } from '../android/androidTest';
|
import { androidTest } from '../android/androidTest';
|
||||||
|
|
@ -35,3 +35,11 @@ if (process.env.PWPAGE_IMPL === 'webview2')
|
||||||
impl = webView2Test;
|
impl = webView2Test;
|
||||||
|
|
||||||
export const test = impl;
|
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 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) {
|
const giveItAChanceToResolve = (page: Page) => rafraf(page, 5);
|
||||||
for (let i = 0; i < 5; i++)
|
|
||||||
await page.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
|
|
||||||
}
|
|
||||||
|
|
||||||
it('element state checks should work as expected for label with zero-sized input', async ({ page, server }) => {
|
it('element state checks should work as expected for label with zero-sized input', async ({ page, server }) => {
|
||||||
await page.setContent(`
|
await page.setContent(`
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import type { Page } from 'playwright-core';
|
import type { Page } from 'playwright-core';
|
||||||
import { test as it, expect } from './pageTest';
|
import { test as it, expect, rafraf } from './pageTest';
|
||||||
|
|
||||||
it.skip(({ isAndroid }) => {
|
it.skip(({ isAndroid }) => {
|
||||||
return 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());
|
document.querySelector('div').addEventListener('wheel', e => e.preventDefault());
|
||||||
});
|
});
|
||||||
// Give wheel listener a chance to propagate through all the layers in Firefox.
|
// Give wheel listener a chance to propagate through all the layers in Firefox.
|
||||||
for (let i = 0; i < 10; i++)
|
await rafraf(page, 10);
|
||||||
await page.evaluate(() => new Promise(x => requestAnimationFrame(() => requestAnimationFrame(x))));
|
|
||||||
await page.mouse.wheel(0, 100);
|
await page.mouse.wheel(0, 100);
|
||||||
await expectEvent(page, {
|
await expectEvent(page, {
|
||||||
deltaX: 0,
|
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)');
|
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({
|
const { page } = await runUITest({
|
||||||
'a.spec.ts': `
|
'a.spec.ts': `
|
||||||
import { test, expect } from '@playwright/test';
|
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>');
|
await page.setContent('<button>Click me</button>');
|
||||||
const button = page.getByRole('button', { name: 'Click me' });
|
const button = page.getByRole('button', { name: 'Click me' });
|
||||||
await button.evaluate(node => node.addEventListener('click', () => {
|
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');
|
console.log('I was logged');
|
||||||
await button.click();
|
await button.click();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue