touchpress 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -19
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +2 -2
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +10 -10
- package/dist/{preflight--Z-REtl-.d.mts → preflight-CCKYplYi.d.mts} +8 -8
- package/dist/{preflight-C83jCNPs.mjs → preflight-DzSceNMx.mjs} +26 -26
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# touchpress
|
|
2
2
|
|
|
3
3
|
> [!WARNING]
|
|
4
|
-
>
|
|
4
|
+
> touchpress is highly experimental. Use at your own risk.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
touchpress runs e2e tests for mobile apps on the Playwright test runner. It drives a booted simulator or emulator through [`agent-device`](https://agent-device.dev/).
|
|
7
7
|
|
|
8
8
|
```ts
|
|
9
|
-
import { expect, test } from '
|
|
9
|
+
import { expect, test } from 'touchpress';
|
|
10
10
|
|
|
11
11
|
test('the right credentials land on the profile', async ({ device }) => {
|
|
12
12
|
await device.getByTestId('sign-in-link').tap();
|
|
@@ -24,7 +24,7 @@ test('the right credentials land on the profile', async ({ device }) => {
|
|
|
24
24
|
### Install
|
|
25
25
|
|
|
26
26
|
```sh
|
|
27
|
-
pnpm add -D
|
|
27
|
+
pnpm add -D touchpress @playwright/test
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
### Configure
|
|
@@ -32,9 +32,9 @@ pnpm add -D tangere @playwright/test
|
|
|
32
32
|
```ts
|
|
33
33
|
// playwright.config.ts
|
|
34
34
|
import { defineConfig } from '@playwright/test';
|
|
35
|
-
import type {
|
|
35
|
+
import type { TouchpressOptions } from 'touchpress';
|
|
36
36
|
|
|
37
|
-
export default defineConfig<
|
|
37
|
+
export default defineConfig<TouchpressOptions>({
|
|
38
38
|
testDir: 'e2e',
|
|
39
39
|
workers: 1,
|
|
40
40
|
expect: { timeout: 10_000 },
|
|
@@ -50,9 +50,9 @@ export default defineConfig<TangereOptions>({
|
|
|
50
50
|
});
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
Every option
|
|
53
|
+
Every option touchpress adds is a key of its own in `use`. Playwright merges `use` one key at a time, so what the projects share is written once at the top level and a project sets only what differs.
|
|
54
54
|
|
|
55
|
-
`readyWhen` is required. The driver returns from a launch as soon as the native process starts, before the JavaScript bundle has loaded, so
|
|
55
|
+
`readyWhen` is required. The driver returns from a launch as soon as the native process starts, before the JavaScript bundle has loaded, so touchpress waits for that locator before the first test runs.
|
|
56
56
|
|
|
57
57
|
`deviceName` is what `agent-device devices` prints, which for an Android emulator is the AVD name with its underscores shown as spaces. An AVD created as `Pixel_7_API_34` is `Pixel 7 API 34` here.
|
|
58
58
|
|
|
@@ -64,11 +64,11 @@ One worker gets one device. To run more than one, give `deviceName` an array wit
|
|
|
64
64
|
npx playwright test --project=ios
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
`preflight` reads a project's options and reports whether the device they name is booted, so a missing simulator fails once, in about a second, instead of once per test after the launch timeout. Wire it as a setup project per platform that the device projects depend on. [Basics](https://github.com/wobsoriano/
|
|
67
|
+
`preflight` reads a project's options and reports whether the device they name is booted, so a missing simulator fails once, in about a second, instead of once per test after the launch timeout. Wire it as a setup project per platform that the device projects depend on. [Basics](https://github.com/wobsoriano/touchpress/blob/main/docs/basics.md) has the spec, and [`apps/e2e/e2e/preflight.setup.mts`](https://github.com/wobsoriano/touchpress/blob/main/apps/e2e/e2e/preflight.setup.mts) is a working one.
|
|
68
68
|
|
|
69
69
|
## Run the sample project
|
|
70
70
|
|
|
71
|
-
`apps/e2e` is an Expo app with a home, login, and profile route and a fake sign-in. It is the app
|
|
71
|
+
`apps/e2e` is an Expo app with a home, login, and profile route and a fake sign-in. It is the app touchpress is tested against. Build the library first with `vp run -r build` so the app can resolve `dist`, then run these from `apps/e2e`:
|
|
72
72
|
|
|
73
73
|
```sh
|
|
74
74
|
npx expo run:ios --device 'iPhone 17 Pro Max' --no-bundler
|
|
@@ -89,18 +89,18 @@ Leave Metro running for the whole suite. The first build takes several minutes.
|
|
|
89
89
|
|
|
90
90
|
## Docs
|
|
91
91
|
|
|
92
|
-
- [Basics](https://github.com/wobsoriano/
|
|
93
|
-
- [Configuration](https://github.com/wobsoriano/
|
|
94
|
-
- [Locators](https://github.com/wobsoriano/
|
|
95
|
-
- [Assertions](https://github.com/wobsoriano/
|
|
96
|
-
- [Lifecycle](https://github.com/wobsoriano/
|
|
97
|
-
- [Continuous integration](https://github.com/wobsoriano/
|
|
92
|
+
- [Basics](https://github.com/wobsoriano/touchpress/blob/main/docs/basics.md)
|
|
93
|
+
- [Configuration](https://github.com/wobsoriano/touchpress/blob/main/docs/configuration.md)
|
|
94
|
+
- [Locators](https://github.com/wobsoriano/touchpress/blob/main/docs/locators.md)
|
|
95
|
+
- [Assertions](https://github.com/wobsoriano/touchpress/blob/main/docs/assertions.md)
|
|
96
|
+
- [Lifecycle](https://github.com/wobsoriano/touchpress/blob/main/docs/lifecycle.md)
|
|
97
|
+
- [Continuous integration](https://github.com/wobsoriano/touchpress/blob/main/docs/ci.md)
|
|
98
98
|
|
|
99
99
|
## The workspace
|
|
100
100
|
|
|
101
101
|
```
|
|
102
|
-
packages/
|
|
103
|
-
apps/e2e/
|
|
102
|
+
packages/touchpress/ the library, published to npm
|
|
103
|
+
apps/e2e/ touchpress-e2e, an Expo SDK 57 app, private
|
|
104
104
|
docs/ the documentation linked above
|
|
105
105
|
```
|
|
106
106
|
|
package/dist/core/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as Tree, A as deviceNameForSlot, B as ProbeResult, C as openSession, D as ResolvedOptions, E as ReadyQuery, F as StepOptions, G as CaptureOptions, H as formatFailure, I as Typed, J as DeviceInfo, K as DeviceDriver, L as renderTitle, M as ActionRecord, N as ActionSink, O as
|
|
1
|
+
import { $ as Tree, A as deviceNameForSlot, B as ProbeResult, C as openSession, D as ResolvedOptions, E as ReadyQuery, F as StepOptions, G as CaptureOptions, H as formatFailure, I as Typed, J as DeviceInfo, K as DeviceDriver, L as renderTitle, M as ActionRecord, N as ActionSink, O as TOUCHPRESS_DEFAULTS, P as EvidenceFile, Q as Settled, R as silentSink, S as SessionState, T as DeviceChoice, U as probe, V as ProbeTarget, W as Binding, X as OpenRequest, Y as DeviceSelection, Z as ScrollDirection, _ as RoleOptions, _t as Screen, a as ExpectedValue, at as Filter, b as DeviceSession, bt as renderScreen, c as ScrollSearch, ct as TextMatch, d as directionToward, dt as textMatch, et as Check, f as ActionOptions, ft as PinnedRef, g as Locator, gt as Resolution, h as FilterOptions, ht as Rect, i as ErrorInfo, it as evaluate, j as parseDeviceOptions, k as TouchpressOptions, l as ScrollTrail, lt as describeQuery, m as FillOptions, mt as RawSnapshot, n as PreflightReport, nt as Verdict, o as TouchpressError, ot as Query, p as Device, pt as Platform, q as DeviceFailure, r as preflight, rt as describeCheck, s as ScrollDevice, st as Role, t as PreflightDevice, tt as CheckName, u as createScrollSearch, ut as normalizeText, v as TextOptions, vt as ScreenNode, w as sessionName, x as OpenSessionInput, xt as resolve, y as createDevice, yt as parseScreen, z as ProbeOptions } from "../preflight-CCKYplYi.mjs";
|
|
2
2
|
//#region src/core/evidence.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Never throws. A capture that fails records a note and returns, because masking
|
|
@@ -68,4 +68,4 @@ declare function toPixelBox(rect: {
|
|
|
68
68
|
/** Moves a box into the coordinates of a crop taken at `origin`. */
|
|
69
69
|
declare function relativeTo(box: PixelBox, origin: PixelBox): PixelBox;
|
|
70
70
|
//#endregion
|
|
71
|
-
export { type ActionOptions, type ActionRecord, type ActionSink, type Binding, type CaptureOptions, type Check, type CheckName, type CompareOptions, type Comparison, type Device, type DeviceChoice, type DeviceDriver, type DeviceFailure, type DeviceInfo, type DeviceSelection, type DeviceSession, type ErrorInfo, type EvidenceFile, type ExpectedValue, type FillOptions, type Filter, type FilterOptions, type Locator, type OpenRequest, type OpenSessionInput, type PinnedRef, type PixelBox, type Platform, type PreflightDevice, type PreflightReport, type ProbeOptions, type ProbeResult, type ProbeTarget, type Query, type RawSnapshot, type ReadyQuery, type Rect, type Resolution, type ResolvedOptions, type Role, type RoleOptions, type Screen, type ScreenNode, type ScrollDevice, type ScrollDirection, type ScrollSearch, type ScrollTrail, type SessionState, type Settled, type Size, type StepOptions,
|
|
71
|
+
export { type ActionOptions, type ActionRecord, type ActionSink, type Binding, type CaptureOptions, type Check, type CheckName, type CompareOptions, type Comparison, type Device, type DeviceChoice, type DeviceDriver, type DeviceFailure, type DeviceInfo, type DeviceSelection, type DeviceSession, type ErrorInfo, type EvidenceFile, type ExpectedValue, type FillOptions, type Filter, type FilterOptions, type Locator, type OpenRequest, type OpenSessionInput, type PinnedRef, type PixelBox, type Platform, type PreflightDevice, type PreflightReport, type ProbeOptions, type ProbeResult, type ProbeTarget, type Query, type RawSnapshot, type ReadyQuery, type Rect, type Resolution, type ResolvedOptions, type Role, type RoleOptions, type Screen, type ScreenNode, type ScrollDevice, type ScrollDirection, type ScrollSearch, type ScrollTrail, type SessionState, type Settled, type Size, type StepOptions, TOUCHPRESS_DEFAULTS, type TextMatch, type TextOptions, TouchpressError, type TouchpressOptions, type Tree, type Typed, type Verdict, captureEvidence, compareScreenshot, createDevice, createScrollSearch, cropScreenshot, describeCheck, describeQuery, deviceNameForSlot, directionToward, evaluate, formatFailure, normalizeText, openSession, parseDeviceOptions, parseScreen, preflight, probe, relativeTo, renderScreen, renderTitle, resolve, sessionName, silentSink, sizeOf, textMatch, toPixelBox };
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as textMatch, C as describeCheck, D as parseDeviceOptions, E as deviceNameForSlot, O as describeQuery, S as resolve, T as
|
|
2
|
-
export {
|
|
1
|
+
import { A as textMatch, C as describeCheck, D as parseDeviceOptions, E as deviceNameForSlot, O as describeQuery, S as resolve, T as TOUCHPRESS_DEFAULTS, a as sizeOf, b as parseScreen, d as createScrollSearch, f as directionToward, g as sessionName, h as openSession, i as relativeTo, j as TouchpressError, k as normalizeText, l as captureEvidence, m as probe, n as compareScreenshot, o as toPixelBox, p as formatFailure, r as cropScreenshot, t as preflight, u as createDevice, v as renderTitle, w as evaluate, x as renderScreen, y as silentSink } from "../preflight-DzSceNMx.mjs";
|
|
2
|
+
export { TOUCHPRESS_DEFAULTS, TouchpressError, captureEvidence, compareScreenshot, createDevice, createScrollSearch, cropScreenshot, describeCheck, describeQuery, deviceNameForSlot, directionToward, evaluate, formatFailure, normalizeText, openSession, parseDeviceOptions, parseScreen, preflight, probe, relativeTo, renderScreen, renderTitle, resolve, sessionName, silentSink, sizeOf, textMatch, toPixelBox };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { E as ReadyQuery, _t as Screen, a as ExpectedValue, at as Filter, b as DeviceSession, ct as TextMatch, g as Locator, h as FilterOptions, ht as Rect, i as ErrorInfo, k as
|
|
1
|
+
import { E as ReadyQuery, _t as Screen, a as ExpectedValue, at as Filter, b as DeviceSession, ct as TextMatch, g as Locator, h as FilterOptions, ht as Rect, i as ErrorInfo, k as TouchpressOptions, n as PreflightReport, o as TouchpressError, ot as Query, p as Device, pt as Platform, r as preflight, st as Role, t as PreflightDevice, vt as ScreenNode } from "./preflight-CCKYplYi.mjs";
|
|
2
2
|
import { ExpectMatcherState } from "@playwright/test";
|
|
3
3
|
//#region src/playwright/fixtures.d.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Touchpress's options and none of its fixtures, for a setup project that reads the
|
|
6
6
|
* configuration before any session exists, such as one calling `preflight`.
|
|
7
7
|
*
|
|
8
8
|
* `platform`, `app`, and `readyWhen` default to `undefined` rather than to a
|
|
@@ -10,7 +10,7 @@ import { ExpectMatcherState } from "@playwright/test";
|
|
|
10
10
|
* type, and `parseDeviceOptions` rejects `undefined` by name, so a config that
|
|
11
11
|
* forgot a key and one that never set it fail the same way.
|
|
12
12
|
*/
|
|
13
|
-
declare const setupTest: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & object, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions &
|
|
13
|
+
declare const setupTest: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & object, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions & TouchpressOptions>;
|
|
14
14
|
/**
|
|
15
15
|
* `device` is auto so evidence capture runs for every test in a device project,
|
|
16
16
|
* whether or not the body touched it. Its teardown runs before the session's,
|
|
@@ -22,7 +22,7 @@ declare const setupTest: import("@playwright/test").TestType<import("@playwright
|
|
|
22
22
|
*/
|
|
23
23
|
declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & object & {
|
|
24
24
|
device: Device;
|
|
25
|
-
}, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions &
|
|
25
|
+
}, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions & TouchpressOptions & {
|
|
26
26
|
session: DeviceSession;
|
|
27
27
|
}>;
|
|
28
28
|
//#endregion
|
|
@@ -73,4 +73,4 @@ declare const expect: import("@playwright/test").Expect<{
|
|
|
73
73
|
}>;
|
|
74
74
|
}>;
|
|
75
75
|
//#endregion
|
|
76
|
-
export { type Device, type ErrorInfo, type ExpectedValue, type Filter, type FilterOptions, type Locator, type Platform, type PreflightDevice, type PreflightReport, type Query, type ReadyQuery, type Rect, type Role, type Screen, type ScreenNode, type ScreenshotOptions,
|
|
76
|
+
export { type Device, type ErrorInfo, type ExpectedValue, type Filter, type FilterOptions, type Locator, type Platform, type PreflightDevice, type PreflightReport, type Query, type ReadyQuery, type Rect, type Role, type Screen, type ScreenNode, type ScreenshotOptions, type TextMatch, TouchpressError, type TouchpressOptions, expect, preflight, setupTest, test };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as textMatch, D as parseDeviceOptions, S as resolve, T as
|
|
1
|
+
import { A as textMatch, D as parseDeviceOptions, S as resolve, T as TOUCHPRESS_DEFAULTS, _ as sleep, a as sizeOf, c as createClient, h as openSession, i as relativeTo, j as TouchpressError, l as captureEvidence, n as compareScreenshot, o as toPixelBox, r as cropScreenshot, s as createAgentDeviceDriver, t as preflight, u as createDevice, y as silentSink } from "./preflight-DzSceNMx.mjs";
|
|
2
2
|
import { expect as expect$1, test as test$1 } from "@playwright/test";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { dirname } from "node:path";
|
|
@@ -6,7 +6,7 @@ import { dirname } from "node:path";
|
|
|
6
6
|
const SESSION_FIXTURE_TIMEOUT_MS = 18e4;
|
|
7
7
|
const DEVICE_FIXTURE_TIMEOUT_MS = 12e4;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Touchpress's options and none of its fixtures, for a setup project that reads the
|
|
10
10
|
* configuration before any session exists, such as one calling `preflight`.
|
|
11
11
|
*
|
|
12
12
|
* `platform`, `app`, and `readyWhen` default to `undefined` rather than to a
|
|
@@ -35,31 +35,31 @@ const setupTest = test$1.extend({
|
|
|
35
35
|
option: true,
|
|
36
36
|
scope: "worker"
|
|
37
37
|
}],
|
|
38
|
-
relaunch: [
|
|
38
|
+
relaunch: [TOUCHPRESS_DEFAULTS.relaunch, {
|
|
39
39
|
option: true,
|
|
40
40
|
scope: "worker"
|
|
41
41
|
}],
|
|
42
|
-
onDeviceInUse: [
|
|
42
|
+
onDeviceInUse: [TOUCHPRESS_DEFAULTS.onDeviceInUse, {
|
|
43
43
|
option: true,
|
|
44
44
|
scope: "worker"
|
|
45
45
|
}],
|
|
46
|
-
settleQuietMs: [
|
|
46
|
+
settleQuietMs: [TOUCHPRESS_DEFAULTS.settleQuietMs, {
|
|
47
47
|
option: true,
|
|
48
48
|
scope: "worker"
|
|
49
49
|
}],
|
|
50
|
-
launchTimeout: [
|
|
50
|
+
launchTimeout: [TOUCHPRESS_DEFAULTS.launchTimeout, {
|
|
51
51
|
option: true,
|
|
52
52
|
scope: "worker"
|
|
53
53
|
}],
|
|
54
|
-
dismissDevOverlay: [
|
|
54
|
+
dismissDevOverlay: [TOUCHPRESS_DEFAULTS.dismissDevOverlay, {
|
|
55
55
|
option: true,
|
|
56
56
|
scope: "worker"
|
|
57
57
|
}],
|
|
58
|
-
evidence: [
|
|
58
|
+
evidence: [TOUCHPRESS_DEFAULTS.evidence, {
|
|
59
59
|
option: true,
|
|
60
60
|
scope: "worker"
|
|
61
61
|
}],
|
|
62
|
-
sessionPrefix: [
|
|
62
|
+
sessionPrefix: [TOUCHPRESS_DEFAULTS.sessionPrefix, {
|
|
63
63
|
option: true,
|
|
64
64
|
scope: "worker"
|
|
65
65
|
}]
|
|
@@ -436,4 +436,4 @@ const expect = expect$1.extend({
|
|
|
436
436
|
}
|
|
437
437
|
});
|
|
438
438
|
//#endregion
|
|
439
|
-
export {
|
|
439
|
+
export { TouchpressError, expect, preflight, setupTest, test };
|
|
@@ -323,7 +323,7 @@ type CaptureOptions = {
|
|
|
323
323
|
* Contract every implementation owes the core. `open` converges when called on
|
|
324
324
|
* an already-open session. Mutations take a `PinnedRef` and never a selector, so
|
|
325
325
|
* the driver's own matcher is never a second opinion on which node was meant.
|
|
326
|
-
* Every failure throws a `
|
|
326
|
+
* Every failure throws a `TouchpressError` carrying a `DeviceFailure`.
|
|
327
327
|
*/
|
|
328
328
|
type DeviceDriver = {
|
|
329
329
|
/** Takes no session, so it is the one call that binds nothing. */
|
|
@@ -475,14 +475,14 @@ declare const silentSink: ActionSink;
|
|
|
475
475
|
//#endregion
|
|
476
476
|
//#region src/core/config.d.ts
|
|
477
477
|
/**
|
|
478
|
-
* The keys
|
|
478
|
+
* The keys touchpress adds to Playwright's `use`. Each is its own option fixture, so
|
|
479
479
|
* a project overrides one without restating the rest.
|
|
480
480
|
*
|
|
481
481
|
* Playwright types `use` as a partial of this, so a config may leave any key out.
|
|
482
482
|
* `parseDeviceOptions` is what makes `platform`, `app`, and `readyWhen` required.
|
|
483
483
|
* It runs once at worker start and nothing downstream re-validates.
|
|
484
484
|
*/
|
|
485
|
-
type
|
|
485
|
+
type TouchpressOptions = {
|
|
486
486
|
/** Required. */
|
|
487
487
|
platform: Platform | undefined;
|
|
488
488
|
/** Required. Bundle id on iOS, package name on Android. Never a path to an artifact. */
|
|
@@ -513,7 +513,7 @@ type TangereOptions = {
|
|
|
513
513
|
dismissDevOverlay: boolean;
|
|
514
514
|
/** @default 'on-failure' */
|
|
515
515
|
evidence: 'on-failure' | 'always' | 'off';
|
|
516
|
-
/** @default '
|
|
516
|
+
/** @default 'touchpress'. Session names are `${prefix}-${project}-${parallelIndex}`. */
|
|
517
517
|
sessionPrefix: string;
|
|
518
518
|
};
|
|
519
519
|
/**
|
|
@@ -521,7 +521,7 @@ type TangereOptions = {
|
|
|
521
521
|
* to the same values, so a caller that reaches the parser without the fixtures,
|
|
522
522
|
* such as `preflight`, resolves identically.
|
|
523
523
|
*/
|
|
524
|
-
declare const
|
|
524
|
+
declare const TOUCHPRESS_DEFAULTS: Omit<TouchpressOptions, 'platform' | 'app' | 'readyWhen' | 'deviceName' | 'launchUrl'>;
|
|
525
525
|
/** The config-file friendly subset of a locator. Matched by the same rules as any other query. */
|
|
526
526
|
type ReadyQuery = {
|
|
527
527
|
text: string;
|
|
@@ -826,7 +826,7 @@ type ErrorInfo = {
|
|
|
826
826
|
readonly command: string;
|
|
827
827
|
readonly failure: DeviceFailure;
|
|
828
828
|
};
|
|
829
|
-
declare class
|
|
829
|
+
declare class TouchpressError extends Error {
|
|
830
830
|
readonly info: ErrorInfo;
|
|
831
831
|
constructor(info: ErrorInfo);
|
|
832
832
|
}
|
|
@@ -848,6 +848,6 @@ type PreflightReport = {
|
|
|
848
848
|
* runner names every one of them at once. A malformed config still throws, the
|
|
849
849
|
* way it does everywhere else. `driver` is the seam tests inject.
|
|
850
850
|
*/
|
|
851
|
-
declare function preflight(options: Partial<
|
|
851
|
+
declare function preflight(options: Partial<TouchpressOptions>, driver?: DeviceDriver): Promise<PreflightReport>;
|
|
852
852
|
//#endregion
|
|
853
|
-
export { Tree as $, deviceNameForSlot as A, ProbeResult as B, openSession as C, ResolvedOptions as D, ReadyQuery as E, StepOptions as F, CaptureOptions as G, formatFailure as H, Typed as I, DeviceInfo as J, DeviceDriver as K, renderTitle as L, ActionRecord as M, ActionSink as N,
|
|
853
|
+
export { Tree as $, deviceNameForSlot as A, ProbeResult as B, openSession as C, ResolvedOptions as D, ReadyQuery as E, StepOptions as F, CaptureOptions as G, formatFailure as H, Typed as I, DeviceInfo as J, DeviceDriver as K, renderTitle as L, ActionRecord as M, ActionSink as N, TOUCHPRESS_DEFAULTS as O, EvidenceFile as P, Settled as Q, silentSink as R, SessionState as S, DeviceChoice as T, probe as U, ProbeTarget as V, Binding as W, OpenRequest as X, DeviceSelection as Y, ScrollDirection as Z, RoleOptions as _, Screen as _t, ExpectedValue as a, Filter as at, DeviceSession as b, renderScreen as bt, ScrollSearch as c, TextMatch as ct, directionToward as d, textMatch as dt, Check as et, ActionOptions as f, PinnedRef as ft, Locator as g, Resolution as gt, FilterOptions as h, Rect as ht, ErrorInfo as i, evaluate as it, parseDeviceOptions as j, TouchpressOptions as k, ScrollTrail as l, describeQuery as lt, FillOptions as m, RawSnapshot as mt, PreflightReport as n, Verdict as nt, TouchpressError as o, Query as ot, Device as p, Platform as pt, DeviceFailure as q, preflight as r, describeCheck as rt, ScrollDevice as s, Role as st, PreflightDevice as t, CheckName as tt, createScrollSearch as u, normalizeText as ut, TextOptions as v, ScreenNode as vt, sessionName as w, OpenSessionInput as x, resolve as xt, createDevice as y, parseScreen as yt, ProbeOptions as z };
|
|
@@ -2,17 +2,17 @@ import { createAgentDeviceClient, normalizeAgentDeviceError } from "agent-device
|
|
|
2
2
|
import pixelmatch from "pixelmatch";
|
|
3
3
|
import { PNG } from "pngjs";
|
|
4
4
|
//#region src/core/errors.ts
|
|
5
|
-
var
|
|
5
|
+
var TouchpressError = class extends Error {
|
|
6
6
|
info;
|
|
7
7
|
constructor(info) {
|
|
8
8
|
super(formatError(info));
|
|
9
|
-
this.name = "
|
|
9
|
+
this.name = "TouchpressError";
|
|
10
10
|
this.info = info;
|
|
11
11
|
}
|
|
12
12
|
};
|
|
13
13
|
function formatError(info) {
|
|
14
14
|
switch (info.kind) {
|
|
15
|
-
case "config": return `Invalid
|
|
15
|
+
case "config": return `Invalid touchpress option: use.${info.field} ${info.detail}`;
|
|
16
16
|
case "device-in-use": return [
|
|
17
17
|
`Device ${info.device} is held by ${info.owner === null ? "another session" : `session "${info.owner}"`}.`,
|
|
18
18
|
`Release it with: ${info.releaseCommand}`,
|
|
@@ -183,14 +183,14 @@ function describeIndex(index) {
|
|
|
183
183
|
* to the same values, so a caller that reaches the parser without the fixtures,
|
|
184
184
|
* such as `preflight`, resolves identically.
|
|
185
185
|
*/
|
|
186
|
-
const
|
|
186
|
+
const TOUCHPRESS_DEFAULTS = {
|
|
187
187
|
relaunch: "per-test",
|
|
188
188
|
onDeviceInUse: "fail",
|
|
189
189
|
settleQuietMs: 500,
|
|
190
190
|
launchTimeout: 9e4,
|
|
191
191
|
dismissDevOverlay: false,
|
|
192
192
|
evidence: "on-failure",
|
|
193
|
-
sessionPrefix: "
|
|
193
|
+
sessionPrefix: "touchpress"
|
|
194
194
|
};
|
|
195
195
|
const DEFAULT_ACTION_TIMEOUT_MS = 1e4;
|
|
196
196
|
const ROLES = [
|
|
@@ -226,18 +226,18 @@ function parseDeviceOptions(raw) {
|
|
|
226
226
|
readyWhen: parseReadyWhen(read(raw, "readyWhen")),
|
|
227
227
|
device: parseDeviceChoice(read(raw, "deviceName")),
|
|
228
228
|
launchUrl: optionalText("launchUrl", read(raw, "launchUrl")),
|
|
229
|
-
relaunch: oneOf("relaunch", read(raw, "relaunch"), ["per-test", "per-worker"],
|
|
230
|
-
onDeviceInUse: oneOf("onDeviceInUse", read(raw, "onDeviceInUse"), ["fail", "reclaim"],
|
|
229
|
+
relaunch: oneOf("relaunch", read(raw, "relaunch"), ["per-test", "per-worker"], TOUCHPRESS_DEFAULTS.relaunch),
|
|
230
|
+
onDeviceInUse: oneOf("onDeviceInUse", read(raw, "onDeviceInUse"), ["fail", "reclaim"], TOUCHPRESS_DEFAULTS.onDeviceInUse),
|
|
231
231
|
actionTimeout: parseActionTimeout(read(raw, "actionTimeout")),
|
|
232
|
-
settleQuietMs: positive("settleQuietMs", read(raw, "settleQuietMs"),
|
|
233
|
-
launchTimeout: positive("launchTimeout", read(raw, "launchTimeout"),
|
|
234
|
-
dismissDevOverlay: flag("dismissDevOverlay", read(raw, "dismissDevOverlay"),
|
|
232
|
+
settleQuietMs: positive("settleQuietMs", read(raw, "settleQuietMs"), TOUCHPRESS_DEFAULTS.settleQuietMs),
|
|
233
|
+
launchTimeout: positive("launchTimeout", read(raw, "launchTimeout"), TOUCHPRESS_DEFAULTS.launchTimeout),
|
|
234
|
+
dismissDevOverlay: flag("dismissDevOverlay", read(raw, "dismissDevOverlay"), TOUCHPRESS_DEFAULTS.dismissDevOverlay),
|
|
235
235
|
evidence: oneOf("evidence", read(raw, "evidence"), [
|
|
236
236
|
"on-failure",
|
|
237
237
|
"always",
|
|
238
238
|
"off"
|
|
239
|
-
],
|
|
240
|
-
sessionPrefix: text("sessionPrefix", read(raw, "sessionPrefix"),
|
|
239
|
+
], TOUCHPRESS_DEFAULTS.evidence),
|
|
240
|
+
sessionPrefix: text("sessionPrefix", read(raw, "sessionPrefix"), TOUCHPRESS_DEFAULTS.sessionPrefix)
|
|
241
241
|
};
|
|
242
242
|
}
|
|
243
243
|
/** A non-object source reads as every key unset, so a caller that passes nothing fails on the first required key. */
|
|
@@ -246,7 +246,7 @@ function read(source, key) {
|
|
|
246
246
|
return key in source ? Reflect.get(source, key) : void 0;
|
|
247
247
|
}
|
|
248
248
|
/**
|
|
249
|
-
* Playwright's own `use.actionTimeout`, not one of
|
|
249
|
+
* Playwright's own `use.actionTimeout`, not one of touchpress's. Playwright defaults
|
|
250
250
|
* it to 0, which means "no timeout" there and would mean "give up at once" here,
|
|
251
251
|
* so 0 falls back the way an unset value does.
|
|
252
252
|
*/
|
|
@@ -348,7 +348,7 @@ function text(field, value, fallback) {
|
|
|
348
348
|
return value;
|
|
349
349
|
}
|
|
350
350
|
function fail(field, detail) {
|
|
351
|
-
return new
|
|
351
|
+
return new TouchpressError({
|
|
352
352
|
kind: "config",
|
|
353
353
|
field,
|
|
354
354
|
detail
|
|
@@ -806,7 +806,7 @@ async function openWithRecovery(driver, options, name, deviceName) {
|
|
|
806
806
|
if (failure === null) throw error;
|
|
807
807
|
if (failure.kind === "device-busy") {
|
|
808
808
|
const owner = failure.owner;
|
|
809
|
-
if (!(owner !== null && (owner.startsWith(options.sessionPrefix) || options.onDeviceInUse === "reclaim"))) throw new
|
|
809
|
+
if (!(owner !== null && (owner.startsWith(options.sessionPrefix) || options.onDeviceInUse === "reclaim"))) throw new TouchpressError({
|
|
810
810
|
kind: "device-in-use",
|
|
811
811
|
owner,
|
|
812
812
|
device: deviceName ?? options.platform,
|
|
@@ -820,7 +820,7 @@ async function openWithRecovery(driver, options, name, deviceName) {
|
|
|
820
820
|
await driver.close(name);
|
|
821
821
|
return await driver.open(request);
|
|
822
822
|
}
|
|
823
|
-
throw new
|
|
823
|
+
throw new TouchpressError({
|
|
824
824
|
kind: "launch-failed",
|
|
825
825
|
app: options.app,
|
|
826
826
|
device: deviceName ?? options.platform,
|
|
@@ -877,7 +877,7 @@ function createSession(driver, options, name, binding) {
|
|
|
877
877
|
if (remaining <= 0) break;
|
|
878
878
|
await sleep(Math.min(READY_POLL_MS, remaining));
|
|
879
879
|
}
|
|
880
|
-
throw new
|
|
880
|
+
throw new TouchpressError({
|
|
881
881
|
kind: "not-ready",
|
|
882
882
|
locator: describeQuery(options.readyWhen),
|
|
883
883
|
timeoutMs: Date.now() - started,
|
|
@@ -920,12 +920,12 @@ function createSession(driver, options, name, binding) {
|
|
|
920
920
|
};
|
|
921
921
|
}
|
|
922
922
|
function unusable(state) {
|
|
923
|
-
if (state.phase === "broken") return new
|
|
923
|
+
if (state.phase === "broken") return new TouchpressError({
|
|
924
924
|
kind: "driver",
|
|
925
925
|
command: "device command",
|
|
926
926
|
failure: state.failure
|
|
927
927
|
});
|
|
928
|
-
return new
|
|
928
|
+
return new TouchpressError({
|
|
929
929
|
kind: "session-closed",
|
|
930
930
|
command: "run a device command"
|
|
931
931
|
});
|
|
@@ -943,7 +943,7 @@ function sessionName(options, project, slot) {
|
|
|
943
943
|
return `${options.sessionPrefix}-${project === "" ? "default" : project}-${String(slot)}`;
|
|
944
944
|
}
|
|
945
945
|
function failureOf(error) {
|
|
946
|
-
if (!(error instanceof
|
|
946
|
+
if (!(error instanceof TouchpressError)) return null;
|
|
947
947
|
if (error.info.kind === "driver") return error.info.failure;
|
|
948
948
|
if (error.info.kind === "launch-failed") return error.info.failure;
|
|
949
949
|
return null;
|
|
@@ -1240,7 +1240,7 @@ function perform(session, sink, record, options, dispatch, write) {
|
|
|
1240
1240
|
let target = record.query;
|
|
1241
1241
|
let lastActual = null;
|
|
1242
1242
|
let screen = await device.capture();
|
|
1243
|
-
const unconfirmed = (expected) => new
|
|
1243
|
+
const unconfirmed = (expected) => new TouchpressError({
|
|
1244
1244
|
kind: "fill-unconfirmed",
|
|
1245
1245
|
locator,
|
|
1246
1246
|
expected,
|
|
@@ -1288,7 +1288,7 @@ function perform(session, sink, record, options, dispatch, write) {
|
|
|
1288
1288
|
continue;
|
|
1289
1289
|
}
|
|
1290
1290
|
const remaining = deadline - Date.now();
|
|
1291
|
-
if (remaining <= 0) throw new
|
|
1291
|
+
if (remaining <= 0) throw new TouchpressError({
|
|
1292
1292
|
kind: "not-found",
|
|
1293
1293
|
locator,
|
|
1294
1294
|
timeoutMs: timeout,
|
|
@@ -1326,7 +1326,7 @@ function scrollIntoView(session, sink, query, options) {
|
|
|
1326
1326
|
if (resolution.outcome === "many") throw ambiguous(locator, resolution.nodes, screen);
|
|
1327
1327
|
if (resolution.outcome === "one") return;
|
|
1328
1328
|
const remaining = deadline - Date.now();
|
|
1329
|
-
if (remaining <= 0 || !await search.step(screen, query, remaining)) throw new
|
|
1329
|
+
if (remaining <= 0 || !await search.step(screen, query, remaining)) throw new TouchpressError({
|
|
1330
1330
|
kind: "not-found",
|
|
1331
1331
|
locator,
|
|
1332
1332
|
timeoutMs: timeout,
|
|
@@ -1349,7 +1349,7 @@ function reportingScrolls(device, sink) {
|
|
|
1349
1349
|
};
|
|
1350
1350
|
}
|
|
1351
1351
|
function ambiguous(locator, nodes, screen) {
|
|
1352
|
-
return new
|
|
1352
|
+
return new TouchpressError({
|
|
1353
1353
|
kind: "strict-mode",
|
|
1354
1354
|
locator,
|
|
1355
1355
|
matches: nodes.map((node) => describeNode(node)),
|
|
@@ -1495,7 +1495,7 @@ function createAgentDeviceDriver(client, session, selection) {
|
|
|
1495
1495
|
try {
|
|
1496
1496
|
return await body();
|
|
1497
1497
|
} catch (error) {
|
|
1498
|
-
throw new
|
|
1498
|
+
throw new TouchpressError({
|
|
1499
1499
|
kind: "driver",
|
|
1500
1500
|
command,
|
|
1501
1501
|
failure: classifyError(error)
|
|
@@ -1862,4 +1862,4 @@ function notBooted(platform, name, booted) {
|
|
|
1862
1862
|
return `No booted ${platform} device is named '${name}'. Booted right now: ${booted.map((device) => `'${device.name}'`).join(", ")}. Set use.deviceName to one of those or boot '${name}'.`;
|
|
1863
1863
|
}
|
|
1864
1864
|
//#endregion
|
|
1865
|
-
export { textMatch as A, describeCheck as C, parseDeviceOptions as D, deviceNameForSlot as E, describeQuery as O, resolve as S,
|
|
1865
|
+
export { textMatch as A, describeCheck as C, parseDeviceOptions as D, deviceNameForSlot as E, describeQuery as O, resolve as S, TOUCHPRESS_DEFAULTS as T, sleep as _, sizeOf as a, parseScreen as b, createClient as c, createScrollSearch as d, directionToward as f, sessionName as g, openSession as h, relativeTo as i, TouchpressError as j, normalizeText as k, captureEvidence as l, probe as m, compareScreenshot as n, toPixelBox as o, formatFailure as p, cropScreenshot as r, createAgentDeviceDriver as s, preflight as t, createDevice as u, renderTitle as v, evaluate as w, renderScreen as x, silentSink as y };
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "touchpress",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "End-to-end testing for mobile apps.",
|
|
5
|
-
"homepage": "https://github.com/wobsoriano/
|
|
5
|
+
"homepage": "https://github.com/wobsoriano/touchpress#readme",
|
|
6
6
|
"bugs": {
|
|
7
|
-
"url": "https://github.com/wobsoriano/
|
|
7
|
+
"url": "https://github.com/wobsoriano/touchpress/issues"
|
|
8
8
|
},
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"author": "Robert Soriano",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/wobsoriano/
|
|
13
|
+
"url": "git+https://github.com/wobsoriano/touchpress.git"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"dist"
|