touchpress 0.0.1

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 ADDED
@@ -0,0 +1,111 @@
1
+ # tangere
2
+
3
+ > [!WARNING]
4
+ > tangere is highly experimental. Use at your own risk.
5
+
6
+ tangere 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
+
8
+ ```ts
9
+ import { expect, test } from 'tangere';
10
+
11
+ test('the right credentials land on the profile', async ({ device }) => {
12
+ await device.getByTestId('sign-in-link').tap();
13
+ await device.getByRole('text-field', { name: 'Email' }).fill('rob@example.com');
14
+ await device.getByTestId('password').fill('hunter2', { secret: true });
15
+ await device.getByRole('button', { name: 'Sign in' }).tap();
16
+
17
+ await expect(device.getByTestId('signing-in')).toBeVisible();
18
+ await expect(device.getByTestId('profile-email')).toHaveText('rob@example.com', { exact: true });
19
+ });
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ### Install
25
+
26
+ ```sh
27
+ pnpm add -D tangere @playwright/test
28
+ ```
29
+
30
+ ### Configure
31
+
32
+ ```ts
33
+ // playwright.config.ts
34
+ import { defineConfig } from '@playwright/test';
35
+ import type { TangereOptions } from 'tangere';
36
+
37
+ export default defineConfig<TangereOptions>({
38
+ testDir: 'e2e',
39
+ workers: 1,
40
+ expect: { timeout: 10_000 },
41
+ reporter: [['list'], ['html', { open: 'never' }]],
42
+ use: {
43
+ app: 'com.example.app',
44
+ readyWhen: { testId: 'home' },
45
+ },
46
+ projects: [
47
+ { name: 'ios', use: { platform: 'ios', deviceName: 'iPhone 17 Pro Max' } },
48
+ { name: 'android', use: { platform: 'android', deviceName: 'Pixel 7 API 34' } },
49
+ ],
50
+ });
51
+ ```
52
+
53
+ Every option tangere 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
+
55
+ `readyWhen` is required. The driver returns from a launch as soon as the native process starts, before the JavaScript bundle has loaded, so tangere waits for that locator before the first test runs.
56
+
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
+
59
+ One worker gets one device. To run more than one, give `deviceName` an array with an entry per worker.
60
+
61
+ ### Run
62
+
63
+ ```sh
64
+ npx playwright test --project=ios
65
+ ```
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/tangere/blob/main/docs/basics.md) has the spec, and [`apps/e2e/e2e/preflight.setup.mts`](https://github.com/wobsoriano/tangere/blob/main/apps/e2e/e2e/preflight.setup.mts) is a working one.
68
+
69
+ ## Run the sample project
70
+
71
+ `apps/e2e` is an Expo app with a home, login, and profile route and a fake sign-in. It is the app tangere 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
+
73
+ ```sh
74
+ npx expo run:ios --device 'iPhone 17 Pro Max' --no-bundler
75
+ npx expo start --port 8081
76
+ npx playwright test --project=ios
77
+ ```
78
+
79
+ Android works the same way. `expo run:android` takes the AVD's own name with underscores, not the spaced name the test config uses, and the emulator needs `adb reverse` to reach Metro on the host.
80
+
81
+ ```sh
82
+ npx expo run:android --device Expo_API_36 --no-bundler
83
+ adb reverse tcp:8081 tcp:8081
84
+ npx expo start --port 8081
85
+ npx playwright test --project=android
86
+ ```
87
+
88
+ Leave Metro running for the whole suite. The first build takes several minutes.
89
+
90
+ ## Docs
91
+
92
+ - [Basics](https://github.com/wobsoriano/tangere/blob/main/docs/basics.md)
93
+ - [Configuration](https://github.com/wobsoriano/tangere/blob/main/docs/configuration.md)
94
+ - [Locators](https://github.com/wobsoriano/tangere/blob/main/docs/locators.md)
95
+ - [Assertions](https://github.com/wobsoriano/tangere/blob/main/docs/assertions.md)
96
+ - [Lifecycle](https://github.com/wobsoriano/tangere/blob/main/docs/lifecycle.md)
97
+ - [Continuous integration](https://github.com/wobsoriano/tangere/blob/main/docs/ci.md)
98
+
99
+ ## The workspace
100
+
101
+ ```
102
+ packages/tangere/ the library, published to npm
103
+ apps/e2e/ tangere-e2e, an Expo SDK 57 app, private
104
+ docs/ the documentation linked above
105
+ ```
106
+
107
+ `vp check`, `vp test`, and `vp run -r build` at the root cover every package. The end-to-end suite is separate because it needs a device.
108
+
109
+ ## License
110
+
111
+ MIT
@@ -0,0 +1,71 @@
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 TANGERE_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 TangereOptions, l as ScrollTrail, lt as describeQuery, m as FillOptions, mt as RawSnapshot, n as PreflightReport, nt as Verdict, o as TangereError, 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--Z-REtl-.mjs";
2
+ //#region src/core/evidence.d.ts
3
+ /**
4
+ * Never throws. A capture that fails records a note and returns, because masking
5
+ * the test's real error with a screenshot error is worse than no screenshot.
6
+ */
7
+ declare function captureEvidence(session: DeviceSession, sink: ActionSink): Promise<void>;
8
+ //#endregion
9
+ //#region src/core/screenshot.d.ts
10
+ /** A rectangle in image pixels, which is what a snapshot rect becomes once it is scaled. */
11
+ type PixelBox = {
12
+ readonly x: number;
13
+ readonly y: number;
14
+ readonly width: number;
15
+ readonly height: number;
16
+ };
17
+ type Size = {
18
+ readonly width: number;
19
+ readonly height: number;
20
+ };
21
+ type CompareOptions = {
22
+ /** pixelmatch's per-pixel colour distance, 0 to 1. Smaller is stricter. */
23
+ readonly threshold: number;
24
+ /** The share of the image allowed to differ before the comparison fails. */
25
+ readonly maxDiffPixelRatio: number;
26
+ /**
27
+ * Painted opaque black in both images before anything is compared. A clock, an
28
+ * avatar, or anything else that changes between runs goes here rather than
29
+ * into a looser threshold, which would blind the whole image.
30
+ */
31
+ readonly mask: readonly PixelBox[];
32
+ };
33
+ /**
34
+ * A size mismatch is its own case rather than a ratio of 1, because nothing
35
+ * about a threshold or a mask can rescue it.
36
+ */
37
+ type Comparison = {
38
+ readonly kind: 'match';
39
+ readonly ratio: number;
40
+ } | {
41
+ readonly kind: 'mismatch';
42
+ readonly ratio: number;
43
+ readonly diff: Buffer;
44
+ } | {
45
+ readonly kind: 'size-mismatch';
46
+ readonly expected: Size;
47
+ readonly actual: Size;
48
+ };
49
+ /**
50
+ * The ratio is mismatched pixels over the image's own pixel count, so it means
51
+ * the same whether the images are a whole device or one cropped button.
52
+ */
53
+ declare function compareScreenshot(expected: Buffer, actual: Buffer, options: CompareOptions): Comparison;
54
+ /**
55
+ * The box is clamped to the image, because a rect comes from a snapshot and a
56
+ * screenshot is a separate capture. A control flush against the bottom edge can
57
+ * round a pixel past it, and that is not a reason to fail an assertion.
58
+ */
59
+ declare function cropScreenshot(source: Buffer, box: PixelBox): Buffer;
60
+ declare function sizeOf(source: Buffer): Size;
61
+ /** Rounded outward, so a control's own edge is never the thing that gets cut off. */
62
+ declare function toPixelBox(rect: {
63
+ readonly x: number;
64
+ readonly y: number;
65
+ readonly width: number;
66
+ readonly height: number;
67
+ }, scale: number): PixelBox;
68
+ /** Moves a box into the coordinates of a crop taken at `origin`. */
69
+ declare function relativeTo(box: PixelBox, origin: PixelBox): PixelBox;
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, TANGERE_DEFAULTS, TangereError, type TangereOptions, type TextMatch, type TextOptions, 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 };
@@ -0,0 +1,2 @@
1
+ import { A as textMatch, C as describeCheck, D as parseDeviceOptions, E as deviceNameForSlot, O as describeQuery, S as resolve, T as TANGERE_DEFAULTS, a as sizeOf, b as parseScreen, d as createScrollSearch, f as directionToward, g as sessionName, h as openSession, i as relativeTo, j as TangereError, 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-C83jCNPs.mjs";
2
+ export { TANGERE_DEFAULTS, TangereError, 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 };
@@ -0,0 +1,76 @@
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 TangereOptions, n as PreflightReport, o as TangereError, ot as Query, p as Device, pt as Platform, r as preflight, st as Role, t as PreflightDevice, vt as ScreenNode } from "./preflight--Z-REtl-.mjs";
2
+ import { ExpectMatcherState } from "@playwright/test";
3
+ //#region src/playwright/fixtures.d.ts
4
+ /**
5
+ * Tangere's options and none of its fixtures, for a setup project that reads the
6
+ * configuration before any session exists, such as one calling `preflight`.
7
+ *
8
+ * `platform`, `app`, and `readyWhen` default to `undefined` rather than to a
9
+ * plausible value. A Playwright option fixture needs a default of its declared
10
+ * type, and `parseDeviceOptions` rejects `undefined` by name, so a config that
11
+ * forgot a key and one that never set it fail the same way.
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 & TangereOptions>;
14
+ /**
15
+ * `device` is auto so evidence capture runs for every test in a device project,
16
+ * whether or not the body touched it. Its teardown runs before the session's,
17
+ * inside the separate budget Playwright grants after the test finishes, so a
18
+ * timed-out test still gets a screenshot.
19
+ *
20
+ * Importing and extending `test` launches no browser: `browser`, `context`, and
21
+ * `page` are lazy and non-auto, and nothing here names them.
22
+ */
23
+ declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & object & {
24
+ device: Device;
25
+ }, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions & TangereOptions & {
26
+ session: DeviceSession;
27
+ }>;
28
+ //#endregion
29
+ //#region src/playwright/screenshot.d.ts
30
+ type ScreenshotOptions = {
31
+ timeout?: number;
32
+ /** The share of the image allowed to differ. @default 0.01 */
33
+ maxDiffPixelRatio?: number;
34
+ /** pixelmatch's per-pixel colour distance, 0 to 1. Smaller is stricter. @default 0.2 */
35
+ threshold?: number;
36
+ /** Painted opaque black in both images, for anything that legitimately changes between runs. */
37
+ mask?: Locator[];
38
+ };
39
+ //#endregion
40
+ //#region src/playwright/expect.d.ts
41
+ type MatcherOptions = {
42
+ timeout?: number;
43
+ };
44
+ type TextMatcherOptions = MatcherOptions & {
45
+ exact?: boolean;
46
+ };
47
+ type MatcherResult = {
48
+ pass: boolean;
49
+ message: () => string;
50
+ name: string;
51
+ expected: string;
52
+ actual: string | null;
53
+ };
54
+ /**
55
+ * Playwright's own matcher names on this package's `expect` only. Matcher
56
+ * typing is by the first parameter, so these surface on `expect(locator)` and
57
+ * nothing else.
58
+ */
59
+ declare const expect: import("@playwright/test").Expect<{
60
+ toBeVisible: (this: ExpectMatcherState, locator: Locator, options?: MatcherOptions) => Promise<MatcherResult>;
61
+ toBeEnabled: (this: ExpectMatcherState, locator: Locator, options?: MatcherOptions) => Promise<MatcherResult>;
62
+ toBeSelected: (this: ExpectMatcherState, locator: Locator, options?: MatcherOptions) => Promise<MatcherResult>;
63
+ toBeFocused: (this: ExpectMatcherState, locator: Locator, options?: MatcherOptions) => Promise<MatcherResult>;
64
+ toHaveText(this: ExpectMatcherState, locator: Locator, expected: string | RegExp, options?: TextMatcherOptions): Promise<MatcherResult>;
65
+ toHaveValue(this: ExpectMatcherState, locator: Locator, expected: string | RegExp, options?: MatcherOptions): Promise<MatcherResult>;
66
+ toHaveCount(this: ExpectMatcherState, locator: Locator, expected: number, options?: MatcherOptions): Promise<MatcherResult>;
67
+ toHaveScreenshot(this: ExpectMatcherState, target: Device | Locator, nameOrOptions?: string | ScreenshotOptions, options?: ScreenshotOptions): Promise<{
68
+ pass: boolean;
69
+ message: () => string;
70
+ name: string;
71
+ expected: string;
72
+ actual: string | null;
73
+ }>;
74
+ }>;
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, TangereError, type TangereOptions, type TextMatch, expect, preflight, setupTest, test };