Skip to content

Commit 65ef51a

Browse files
committed
feat: playwright tracing
1 parent caa0ae7 commit 65ef51a

11 files changed

Lines changed: 540 additions & 91 deletions

File tree

e2e/mobilewright.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ function resolveDriver(): DriverConfig {
2727
const config: MobilewrightConfig = defineConfig({
2828
testDir: './src',
2929
testMatch: '**/*.test.ts',
30+
trace: 'on',
3031
retries: 0,
3132
timeout: 60_000,
3233
driver: resolveDriver(),

package-lock.json

Lines changed: 33 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/mobilewright-core/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
],
3131
"dependencies": {
3232
"@mobilewright/protocol": "^0.0.1",
33-
"sharp": "^0.34.5"
33+
"sharp": "^0.34.5",
34+
"yazl": "^3.3.1"
35+
},
36+
"devDependencies": {
37+
"@types/yazl": "^3.3.1"
3438
}
3539
}

packages/mobilewright-core/src/device.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
} from '@mobilewright/protocol';
1111
import { Screen } from './screen.js';
1212
import type { LocatorOptions } from './locator.js';
13+
import type { Tracer } from './tracing.js';
1314

1415
export interface DeviceOptions {
1516
locatorDefaults?: LocatorOptions;
@@ -20,12 +21,19 @@ export class Device {
2021
private cleanupCallbacks: Array<() => Promise<void>> = [];
2122
private _screen: Screen | null = null;
2223
private readonly opts: DeviceOptions;
24+
private _tracer: Tracer | null = null;
2325

2426
constructor(driver: MobilewrightDriver, opts: DeviceOptions = {}) {
2527
this.driver = driver;
2628
this.opts = opts;
2729
}
2830

31+
setTracer(tracer: Tracer): void {
32+
this._tracer = tracer;
33+
tracer.setDriver(this.driver);
34+
this._screen = null; // reset so next access picks up tracer
35+
}
36+
2937
/** Register a callback to run on close(). Used by launchers for cleanup. */
3038
onClose(callback: () => Promise<void>): void {
3139
this.cleanupCallbacks.push(callback);
@@ -51,7 +59,7 @@ export class Device {
5159
}
5260

5361
get screen(): Screen {
54-
this._screen ??= new Screen(this.driver, this.opts.locatorDefaults);
62+
this._screen ??= new Screen(this.driver, this.opts.locatorDefaults, this._tracer);
5563
return this._screen;
5664
}
5765

packages/mobilewright-core/src/expect.ts

Lines changed: 65 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -32,74 +32,103 @@ class LocatorAssertions {
3232
private readonly negated: boolean,
3333
) {}
3434

35+
private async _wrapAssertion<T>(method: string, params: Record<string, unknown>, fn: () => Promise<T>): Promise<T> {
36+
const tracer = this.locator._tracer;
37+
if (!tracer) {
38+
return fn();
39+
}
40+
const label = this.negated ? `not.${method}` : method;
41+
return tracer.wrapAction('Expect', label, params, fn);
42+
}
43+
3544
get not(): LocatorAssertions {
3645
return new LocatorAssertions(this.locator, !this.negated);
3746
}
3847

3948
async toBeVisible(opts?: ExpectOptions): Promise<void> {
40-
await this.assertBoolean('visible', () => this.locator.isVisible({ timeout: 0 }), opts);
49+
return this._wrapAssertion('toBeVisible', {}, async () => {
50+
await this.assertBoolean('visible', () => this.locator.isVisible({ timeout: 0 }), opts);
51+
});
4152
}
4253

4354
async toBeHidden(opts?: ExpectOptions): Promise<void> {
44-
await this.assertBoolean('hidden', async () => {
45-
const visible = await this.locator.isVisible({ timeout: 0 });
46-
return !visible;
47-
}, opts);
55+
return this._wrapAssertion('toBeHidden', {}, async () => {
56+
await this.assertBoolean('hidden', async () => {
57+
const visible = await this.locator.isVisible({ timeout: 0 });
58+
return !visible;
59+
}, opts);
60+
});
4861
}
4962

5063
async toBeEnabled(opts?: ExpectOptions): Promise<void> {
51-
await this.assertBoolean('enabled', () => this.locator.isEnabled({ timeout: 0 }), opts);
64+
return this._wrapAssertion('toBeEnabled', {}, async () => {
65+
await this.assertBoolean('enabled', () => this.locator.isEnabled({ timeout: 0 }), opts);
66+
});
5267
}
5368

5469
async toBeDisabled(opts?: ExpectOptions): Promise<void> {
55-
await this.assertBoolean('disabled', async () => {
56-
const enabled = await this.locator.isEnabled({ timeout: 0 });
57-
return !enabled;
58-
}, opts);
70+
return this._wrapAssertion('toBeDisabled', {}, async () => {
71+
await this.assertBoolean('disabled', async () => {
72+
const enabled = await this.locator.isEnabled({ timeout: 0 });
73+
return !enabled;
74+
}, opts);
75+
});
5976
}
6077

6178
async toBeSelected(opts?: ExpectOptions): Promise<void> {
62-
await this.assertBoolean('selected', () => this.locator.isSelected({ timeout: 0 }), opts);
79+
return this._wrapAssertion('toBeSelected', {}, async () => {
80+
await this.assertBoolean('selected', () => this.locator.isSelected({ timeout: 0 }), opts);
81+
});
6382
}
6483

6584
async toBeFocused(opts?: ExpectOptions): Promise<void> {
66-
await this.assertBoolean('focused', () => this.locator.isFocused({ timeout: 0 }), opts);
85+
return this._wrapAssertion('toBeFocused', {}, async () => {
86+
await this.assertBoolean('focused', () => this.locator.isFocused({ timeout: 0 }), opts);
87+
});
6788
}
6889

6990
async toBeChecked(opts?: ExpectOptions): Promise<void> {
70-
await this.assertBoolean('checked', () => this.locator.isChecked({ timeout: 0 }), opts);
91+
return this._wrapAssertion('toBeChecked', {}, async () => {
92+
await this.assertBoolean('checked', () => this.locator.isChecked({ timeout: 0 }), opts);
93+
});
7194
}
7295

7396
async toHaveText(expected: string | RegExp, opts?: ExpectOptions): Promise<void> {
74-
await this.assertText(
75-
(text) => expected instanceof RegExp ? expected.test(text) : text === expected,
76-
expected, opts,
77-
);
97+
return this._wrapAssertion('toHaveText', { expected: String(expected) }, async () => {
98+
await this.assertText(
99+
(text) => expected instanceof RegExp ? expected.test(text) : text === expected,
100+
expected, opts,
101+
);
102+
});
78103
}
79104

80105
async toContainText(expected: string, opts?: ExpectOptions): Promise<void> {
81-
await this.assertText(
82-
(text) => text.includes(expected),
83-
expected, opts,
84-
);
106+
return this._wrapAssertion('toContainText', { expected }, async () => {
107+
await this.assertText(
108+
(text) => text.includes(expected),
109+
expected, opts,
110+
);
111+
});
85112
}
86113

87114
async toHaveValue(expected: string | RegExp, opts?: ExpectOptions): Promise<void> {
88-
let lastValue = '';
89-
await this.retryUntil(
90-
async () => {
91-
try { lastValue = await this.locator.getValue({ timeout: 0 }); } catch { lastValue = ''; }
92-
return lastValue;
93-
},
94-
(value) => {
95-
const matches = expected instanceof RegExp ? expected.test(value) : value === expected;
96-
return this.negated ? !matches : matches;
97-
},
98-
opts?.timeout ?? DEFAULT_TIMEOUT,
99-
() => this.negated
100-
? `Expected element NOT to have value "${expected}", but got "${lastValue}"`
101-
: `Expected element to have value "${expected}", but got "${lastValue}"`,
102-
);
115+
return this._wrapAssertion('toHaveValue', { expected: String(expected) }, async () => {
116+
let lastValue = '';
117+
await this.retryUntil(
118+
async () => {
119+
try { lastValue = await this.locator.getValue({ timeout: 0 }); } catch { lastValue = ''; }
120+
return lastValue;
121+
},
122+
(value) => {
123+
const matches = expected instanceof RegExp ? expected.test(value) : value === expected;
124+
return this.negated ? !matches : matches;
125+
},
126+
opts?.timeout ?? DEFAULT_TIMEOUT,
127+
() => this.negated
128+
? `Expected element NOT to have value "${expected}", but got "${lastValue}"`
129+
: `Expected element to have value "${expected}", but got "${lastValue}"`,
130+
);
131+
});
103132
}
104133

105134
private async assertBoolean(

packages/mobilewright-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export { Device, type DeviceOptions } from './device.js';
44
export { expect, ExpectError, type ExpectOptions } from './expect.js';
55
export { queryAll, type LocatorStrategy } from './query-engine.js';
66
export { sleep } from './sleep.js';
7+
export { Tracer } from './tracing.js';

0 commit comments

Comments
 (0)