touchpress 0.0.1 → 0.1.0

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 CHANGED
@@ -1,12 +1,12 @@
1
- # tangere
1
+ # touchpress
2
2
 
3
3
  > [!WARNING]
4
- > tangere is highly experimental. Use at your own risk.
4
+ > touchpress is highly experimental. Use at your own risk.
5
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/).
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 'tangere';
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 tangere @playwright/test
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 { TangereOptions } from 'tangere';
35
+ import type { TouchpressOptions } from 'touchpress';
36
36
 
37
- export default defineConfig<TangereOptions>({
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 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.
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 tangere waits for that locator before the first test runs.
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,24 @@ 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/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.
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
+
69
+ ### Drive a step with a model
70
+
71
+ `device.act` takes an instruction in English and drives the app until it is satisfied. `device.extract` asks one question about the screen and returns a typed answer.
72
+
73
+ ```ts
74
+ test('sign in', async ({ device }) => {
75
+ await device.act('Sign in with the email rob@example.com and the password hunter2');
76
+ await expect(device.getByTestId('greeting')).toHaveText('Hi, Rob');
77
+ });
78
+ ```
79
+
80
+ Set `use.aiModel` to a gateway model id or a provider model instance, and install `ai`, an optional peer dependency. A loop can run for minutes, so raise the test timeout on any spec that calls `act`. The example uses the sample app's fake account. Keep real credentials in deterministic `fill(text, { secret: true })` calls outside `act`, and assert deterministically afterwards. [AI](https://github.com/wobsoriano/touchpress/blob/main/docs/ai.md) covers `act`, `extract`, the tools the model gets, and the report.
68
81
 
69
82
  ## Run the sample project
70
83
 
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`:
84
+ `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
85
 
73
86
  ```sh
74
87
  npx expo run:ios --device 'iPhone 17 Pro Max' --no-bundler
@@ -89,18 +102,19 @@ Leave Metro running for the whole suite. The first build takes several minutes.
89
102
 
90
103
  ## Docs
91
104
 
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)
105
+ - [Basics](https://github.com/wobsoriano/touchpress/blob/main/docs/basics.md)
106
+ - [Configuration](https://github.com/wobsoriano/touchpress/blob/main/docs/configuration.md)
107
+ - [Locators](https://github.com/wobsoriano/touchpress/blob/main/docs/locators.md)
108
+ - [Assertions](https://github.com/wobsoriano/touchpress/blob/main/docs/assertions.md)
109
+ - [AI](https://github.com/wobsoriano/touchpress/blob/main/docs/ai.md)
110
+ - [Lifecycle](https://github.com/wobsoriano/touchpress/blob/main/docs/lifecycle.md)
111
+ - [Continuous integration](https://github.com/wobsoriano/touchpress/blob/main/docs/ci.md)
98
112
 
99
113
  ## The workspace
100
114
 
101
115
  ```
102
- packages/tangere/ the library, published to npm
103
- apps/e2e/ tangere-e2e, an Expo SDK 57 app, private
116
+ packages/touchpress/ the library, published to npm
117
+ apps/e2e/ touchpress-e2e, an Expo SDK 57 app, private
104
118
  docs/ the documentation linked above
105
119
  ```
106
120
 
@@ -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 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";
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-Czhj9QFM.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, 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 };
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 };
@@ -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 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 };
1
+ import { A as textMatch, C as renderScreen, D as parseDeviceOptions, E as deviceNameForSlot, O as describeQuery, S as parseScreen, T as TOUCHPRESS_DEFAULTS, a as sizeOf, b as renderTitle, 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 describeCheck, w as resolve, x as silentSink, y as evaluate } from "../preflight-B7KrbqSO.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,43 @@
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";
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$1, n as PreflightReport, o as TouchpressError, ot as Query, p as Device$1, pt as Platform, r as preflight, st as Role, t as PreflightDevice, vt as ScreenNode } from "./preflight-Czhj9QFM.mjs";
2
2
  import { ExpectMatcherState } from "@playwright/test";
3
+ import { FlexibleSchema, LanguageModel } from "ai";
4
+ //#region src/ai/device.d.ts
5
+ /** A loop can run for minutes, so its budget is its own rather than the action timeout a deterministic step takes. */
6
+ type ActOptions = {
7
+ timeout?: number;
8
+ maxSteps?: number;
9
+ };
10
+ type ExtractOptions = {
11
+ timeout?: number;
12
+ };
13
+ type AiDevice = {
14
+ /**
15
+ * Drives the app with a model until the instruction is satisfied. Resolves
16
+ * with the model's summary of what it did, and throws when the model reports
17
+ * it could not proceed or runs out of steps.
18
+ */
19
+ act(instruction: string, options?: ActOptions): Promise<string>;
20
+ /** Asks a model one question about the current screen and validates the answer against `schema`. */
21
+ extract<T>(question: string, schema: FlexibleSchema<T>, options?: ExtractOptions): Promise<T>;
22
+ };
23
+ //#endregion
24
+ //#region src/ai/options.d.ts
25
+ /**
26
+ * The one key `act` and `extract` add to Playwright's `use`. It is not part of
27
+ * `core/config.ts`, because nothing under `core/` may name an AI SDK type.
28
+ *
29
+ * A gateway model id such as `'anthropic/claude-sonnet-5'` and a provider
30
+ * model instance are both `LanguageModel`, so a config picks either without a
31
+ * second key. Unset is the default, and it fails at the first `act` rather than
32
+ * at worker start, so a project that never calls one needs no model.
33
+ */
34
+ type AiOptions = {
35
+ aiModel: LanguageModel | undefined;
36
+ };
37
+ //#endregion
3
38
  //#region src/playwright/fixtures.d.ts
4
39
  /**
5
- * Tangere's options and none of its fixtures, for a setup project that reads the
40
+ * Touchpress's options and none of its fixtures, for a setup project that reads the
6
41
  * configuration before any session exists, such as one calling `preflight`.
7
42
  *
8
43
  * `platform`, `app`, and `readyWhen` default to `undefined` rather than to a
@@ -10,7 +45,7 @@ import { ExpectMatcherState } from "@playwright/test";
10
45
  * type, and `parseDeviceOptions` rejects `undefined` by name, so a config that
11
46
  * forgot a key and one that never set it fail the same way.
12
47
  */
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>;
48
+ 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$1 & AiOptions>;
14
49
  /**
15
50
  * `device` is auto so evidence capture runs for every test in a device project,
16
51
  * whether or not the body touched it. Its teardown runs before the session's,
@@ -21,8 +56,8 @@ declare const setupTest: import("@playwright/test").TestType<import("@playwright
21
56
  * `page` are lazy and non-auto, and nothing here names them.
22
57
  */
23
58
  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 & {
59
+ device: Device$1 & AiDevice;
60
+ }, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions & TouchpressOptions$1 & AiOptions & {
26
61
  session: DeviceSession;
27
62
  }>;
28
63
  //#endregion
@@ -64,7 +99,7 @@ declare const expect: import("@playwright/test").Expect<{
64
99
  toHaveText(this: ExpectMatcherState, locator: Locator, expected: string | RegExp, options?: TextMatcherOptions): Promise<MatcherResult>;
65
100
  toHaveValue(this: ExpectMatcherState, locator: Locator, expected: string | RegExp, options?: MatcherOptions): Promise<MatcherResult>;
66
101
  toHaveCount(this: ExpectMatcherState, locator: Locator, expected: number, options?: MatcherOptions): Promise<MatcherResult>;
67
- toHaveScreenshot(this: ExpectMatcherState, target: Device | Locator, nameOrOptions?: string | ScreenshotOptions, options?: ScreenshotOptions): Promise<{
102
+ toHaveScreenshot(this: ExpectMatcherState, target: Device$1 | Locator, nameOrOptions?: string | ScreenshotOptions, options?: ScreenshotOptions): Promise<{
68
103
  pass: boolean;
69
104
  message: () => string;
70
105
  name: string;
@@ -73,4 +108,13 @@ declare const expect: import("@playwright/test").Expect<{
73
108
  }>;
74
109
  }>;
75
110
  //#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 };
111
+ //#region src/index.d.ts
112
+ /**
113
+ * `Device` and `TouchpressOptions` carry the AI surface here and only here.
114
+ * `touchpress/core` exports the runner-independent pair without it, because
115
+ * nothing under `core/` may name an AI SDK type.
116
+ */
117
+ type TouchpressOptions = TouchpressOptions$1 & AiOptions;
118
+ type Device = Device$1 & AiDevice;
119
+ //#endregion
120
+ export { type ActOptions, type AiDevice, type AiOptions, Device, type ErrorInfo, type ExpectedValue, type ExtractOptions, 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, TouchpressOptions, expect, preflight, setupTest, test };