playthrough 0.2.0 → 0.3.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/src/reporter.js CHANGED
@@ -1 +1 @@
1
- export { t as ACTIONS, n as ASSETS, i as GLIMPSES, a as HANDS, o as HOLD, s as PERSON, c as PICTURE, l as READINGS, u as RECORDING, r as default, d as firstLine, f as glimpsesOf, p as readingsOf, m as readingsOn, h as seconds } from "./reporter-CECNk7aW.js";
1
+ export { t as ACTIONS, n as ASSETS, i as GLIMPSES, a as HANDS, o as HOLD, s as PERSON, c as PICTURE, l as READINGS, u as RECORDING, d as STOPPED_ON, f as UNMET, p as WROTE, r as default, m as firstLine, h as glimpsesOf, g as hashOf, _ as onPage, v as pathOf, y as readingsOf, b as readingsOn, x as seconds } from "./reporter-BEodHYRI.js";
package/src/test.js CHANGED
@@ -1,9 +1,10 @@
1
- import { _ as make, a as REACHES_OUTSIDE, b as repo, h as firstOf, i as ALONGSIDE, n as readPage, o as WALKED, r as unhovered, v as personIn, y as setupIn } from "./read-C9bKWxxP.js";
2
- import { a as HANDS, c as PICTURE, d as firstLine, g as keepAssets, h as seconds, i as GLIMPSES, l as READINGS, n as ASSETS, o as HOLD, s as PERSON, t as ACTIONS, u as RECORDING } from "./reporter-CECNk7aW.js";
1
+ import { S as keepAssets, a as HANDS, c as PICTURE, d as STOPPED_ON, f as UNMET, i as GLIMPSES, l as READINGS, m as firstLine, n as ASSETS, o as HOLD, p as WROTE, s as PERSON, t as ACTIONS, u as RECORDING, v as pathOf, x as seconds } from "./reporter-BEodHYRI.js";
2
+ import { C as make, E as repo, T as setupIn, b as changes, n as REACHES_OUTSIDE, r as WALKED, t as ALONGSIDE, w as personIn, x as firstOf } from "./flows-BcISV1M-.js";
3
+ import { n as readPage, r as unhovered } from "./read-Bg9XVTtn.js";
3
4
  import { readFileSync, writeFileSync } from "node:fs";
4
5
  import { join } from "node:path";
5
- import { pathToFileURL } from "node:url";
6
6
  import { expect as expect$1, test as test$1 } from "@playwright/test";
7
+ import { pathToFileURL } from "node:url";
7
8
  import { AsyncLocalStorage } from "node:async_hooks";
8
9
  // src/rest.ts
9
10
  const STILL = 300;
@@ -82,7 +83,7 @@ function onlyWaits(expect) {
82
83
  if (typeof check !== "function") return check;
83
84
  return (...args) => {
84
85
  try {
85
- return Promise.resolve(check.apply(target, args)).catch(() => {});
86
+ return Promise.resolve(check.apply(target, args)).catch(() => underway && (underway.unmet = (underway.unmet ?? 0) + 1));
86
87
  } catch {}
87
88
  };
88
89
  } });
@@ -124,6 +125,10 @@ const seeded = `(() => {
124
125
  const recorded = test$1.extend({
125
126
  locale: "en-US",
126
127
  timezoneId: "UTC",
128
+ contextOptions: async ({ contextOptions }, use) => use({
129
+ ...(await setupOf()).use,
130
+ ...contextOptions
131
+ }),
127
132
  person: [async ({}, use) => use(firstOf(await setupOf())), { option: true }],
128
133
  rest: async ({ page }, use) => use(run ? await watchRest(page) : async () => {}),
129
134
  setup: async ({ page, request, baseURL, person, rest: _ }, use, testInfo) => {
@@ -151,7 +156,7 @@ const recorded = test$1.extend({
151
156
  await use(async (person, screen = {}) => {
152
157
  const app = await setupOf();
153
158
  personIn(app, person);
154
- const { page, context } = await pageFor(browser, baseURL, screen);
159
+ const { page, context } = await pageFor(browser, baseURL, screen, app.use);
155
160
  opened.push(context);
156
161
  const flow = underway;
157
162
  const rest = run && flow && await watchRest(page);
@@ -185,6 +190,7 @@ const recorded = test$1.extend({
185
190
  await page.exposeFunction("__playthrough", (event) => void (flow.page === page && flow.events.push(event)));
186
191
  flow.assets.push(keepAssets(page, run.folder));
187
192
  pace(page);
193
+ dialogsOn(page, flow);
188
194
  pointer = {
189
195
  page,
190
196
  x: 640,
@@ -192,6 +198,7 @@ const recorded = test$1.extend({
192
198
  };
193
199
  underway = flow;
194
200
  const { events, readings, glimpses, pictures, actions } = flow;
201
+ if (page.url() !== "about:blank") await page.evaluate(() => window.rrwebRecord?.record.takeFullSnapshot());
195
202
  await page.mouse.move(pointer.x, pointer.y);
196
203
  try {
197
204
  await use();
@@ -249,6 +256,18 @@ const recorded = test$1.extend({
249
256
  body: JSON.stringify(flow.hands),
250
257
  contentType: "application/json"
251
258
  });
259
+ if (flow.wrote) await testInfo.attach(WROTE, {
260
+ body: flow.wrote,
261
+ contentType: "text/plain"
262
+ });
263
+ if (flow.unmet) await testInfo.attach(UNMET, {
264
+ body: String(flow.unmet),
265
+ contentType: "text/plain"
266
+ });
267
+ if (!ended && last.url() !== "about:blank") await testInfo.attach(STOPPED_ON, {
268
+ body: pathOf(last.url()),
269
+ contentType: "text/plain"
270
+ });
252
271
  for (const picture of pictures) await testInfo.attach(PICTURE, {
253
272
  body: picture,
254
273
  contentType: "image/png"
@@ -256,12 +275,18 @@ const recorded = test$1.extend({
256
275
  }, { auto: true }]
257
276
  });
258
277
  async function readied(page, run) {
278
+ const host = new URL(run.app.url).hostname;
279
+ page.on("request", (sent) => {
280
+ const flow = underway;
281
+ if (flow && !flow.wrote && changes(sent.method()) && new URL(sent.url()).hostname === host) flow.wrote = `${sent.method()} ${new URL(sent.url()).pathname}`;
282
+ });
259
283
  await page.addInitScript({ content: recorder(run.recorder) });
260
284
  await page.clock.setSystemTime(onReviewDay(run.started));
261
285
  await page.addInitScript({ content: seeded });
262
286
  }
263
- async function pageFor(browser, baseURL, screen) {
287
+ async function pageFor(browser, baseURL, screen, use) {
264
288
  const context = await browser.newContext({
289
+ ...use,
265
290
  baseURL,
266
291
  locale: "en-US",
267
292
  timezoneId: "UTC",
@@ -277,6 +302,7 @@ async function carryOn(flow, page, rest, person, run) {
277
302
  await flow.page.waitForTimeout(HANDING);
278
303
  await page.exposeFunction("__playthrough", (event) => void (flow.page === page && flow.events.push(event)));
279
304
  flow.assets.push(keepAssets(page, run.folder));
305
+ dialogsOn(page, flow);
280
306
  flow.hands.push({
281
307
  at: Date.now(),
282
308
  person
@@ -294,6 +320,70 @@ async function carryOn(flow, page, rest, person, run) {
294
320
  };
295
321
  if (page.url() !== "about:blank") await page.evaluate(() => window.rrwebRecord?.record.takeFullSnapshot());
296
322
  }
323
+ const holding = /* @__PURE__ */ new WeakMap();
324
+ const answered = /* @__PURE__ */ new WeakSet();
325
+ function dialogsOn(page, flow) {
326
+ const listening = (to) => to.listenerCount("dialog");
327
+ page.on("dialog", (dialog) => {
328
+ const theirs = listening(page) > 1 || listening(page.context()) > 0;
329
+ if (dialog.type() === "beforeunload") return void (theirs || dialog.accept().catch(() => {}));
330
+ answers(dialog);
331
+ const said = {
332
+ type: dialog.type(),
333
+ message: dialog.message(),
334
+ ...dialog.type() === "prompt" && { value: dialog.defaultValue() }
335
+ };
336
+ if (flow.page === page) flow.events.push({
337
+ type: 5,
338
+ data: {
339
+ tag: "dialog",
340
+ payload: said
341
+ },
342
+ timestamp: Date.now()
343
+ });
344
+ let answered = () => {};
345
+ flow.dialog = new Promise((done) => answered = done);
346
+ holding.set(dialog, {
347
+ flow,
348
+ page,
349
+ read: new Promise((read) => setTimeout(read, Math.min(4e3, 1200 + 40 * dialog.message().length))),
350
+ answered
351
+ });
352
+ if (!theirs) dialog.dismiss().catch(() => {});
353
+ });
354
+ }
355
+ function answers(dialog) {
356
+ const prototype = Object.getPrototypeOf(dialog);
357
+ if (answered.has(prototype)) return;
358
+ answered.add(prototype);
359
+ for (const [name, yes] of [["accept", true], ["dismiss", false]]) {
360
+ const original = prototype[name];
361
+ prototype[name] = async function(value) {
362
+ const held = holding.get(this);
363
+ holding.delete(this);
364
+ if (held) {
365
+ await held.read;
366
+ const answer = {
367
+ yes,
368
+ ...yes && this.type() === "prompt" && { value: value ?? "" }
369
+ };
370
+ if (held.flow.page === held.page) held.flow.events.push({
371
+ type: 5,
372
+ data: {
373
+ tag: "dialog answered",
374
+ payload: answer
375
+ },
376
+ timestamp: Date.now()
377
+ });
378
+ }
379
+ try {
380
+ return await original.call(this, value);
381
+ } finally {
382
+ held?.answered();
383
+ }
384
+ };
385
+ }
386
+ }
297
387
  let collecting;
298
388
  let around = [];
299
389
  let loading = Promise.resolve();
@@ -346,7 +436,7 @@ async function runAlongside(page, { request, baseURL, started, setup: appSetup,
346
436
  let made;
347
437
  const handTo = async (person, screen = {}) => {
348
438
  personIn(appSetup, person);
349
- const next = await pageFor(page.context().browser(), baseURL, screen);
439
+ const next = await pageFor(page.context().browser(), baseURL, screen, appSetup.use);
350
440
  opened.push(next.context);
351
441
  const rest = await readied(next.page);
352
442
  await appSetup.signIn?.({
@@ -506,6 +596,7 @@ const step = Object.assign(async (title, body, options) => {
506
596
  const flow = underway;
507
597
  if (!flow || flow.depth) return playwrightStep(title, body, options);
508
598
  return playwrightStep(title, async (info) => {
599
+ if (run?.stops?.[test$1.info().title] === title) throw new Error("It stopped here the first time it was recorded.");
509
600
  Object.assign(flow, {
510
601
  step: title,
511
602
  inStep: 0
@@ -520,12 +611,13 @@ const step = Object.assign(async (title, body, options) => {
520
611
  await flow.rest();
521
612
  const at = seconds(Date.now() - (flow.events[0]?.timestamp ?? Date.now()));
522
613
  await unhovered(flow.page, async () => {
523
- const { screen, things } = await readPage(flow.page);
614
+ const { screen, things, items } = await readPage(flow.page);
524
615
  flow.readings.push({
525
616
  step: title,
526
617
  at,
527
618
  screen,
528
- things
619
+ things,
620
+ items
529
621
  });
530
622
  flow.pictures.push(await flow.page.screenshot({
531
623
  animations: "disabled",
@@ -598,7 +690,8 @@ function pace(page) {
598
690
  await pointer.page.mouse.move(pointer.x, pointer.y);
599
691
  await pointer.page.waitForTimeout(HANDING);
600
692
  }
601
- if (pointer) await approach(this, pointer);
693
+ const timeout = args.findLast((arg) => !!arg && typeof arg === "object" && typeof arg.timeout === "number")?.timeout;
694
+ if (pointer) await approach(this, pointer, timeout).catch((error) => Promise.reject(asAction(error, action)));
602
695
  const [text, options] = args;
603
696
  const typed = pointer && action === "fill" && typeof text === "string" && text && await typedInto(this) ? text : void 0;
604
697
  const result = await acted(action, () => pathTo(this), typed ? async () => {
@@ -642,7 +735,12 @@ async function acted(does, target, act, given) {
642
735
  keeping.wrote = false;
643
736
  [on, address] = [await target(keeping.page), keeping.page.url()];
644
737
  }
645
- const result = await act();
738
+ if (underway) underway.dialog = void 0;
739
+ const result = await act().catch(async (error) => {
740
+ const dialog = underway?.dialog;
741
+ if (!dialog) throw error;
742
+ await dialog;
743
+ });
646
744
  underway?.actions.push(Date.now());
647
745
  const walk = run?.walk;
648
746
  if (walk && underway?.depth && !underway.walked && walk.step === underway.step && walk.actions === ++underway.inStep) {
@@ -698,12 +796,13 @@ async function walkOn(flow, walk) {
698
796
  await flow.rest();
699
797
  walked.done.push(since());
700
798
  await unhovered(flow.page, async () => {
701
- const { screen, things } = await readPage(flow.page);
799
+ const { screen, things, items } = await readPage(flow.page);
702
800
  flow.readings.push({
703
801
  step: "In the app",
704
802
  at: since(),
705
803
  screen,
706
- things
804
+ things,
805
+ items
707
806
  });
708
807
  });
709
808
  await look();
@@ -744,7 +843,18 @@ const typedInto = (target) => target.evaluate((on) => on.isContentEditable || on
744
843
  "tel",
745
844
  "password"
746
845
  ].includes(on.type)).catch(() => false);
747
- async function approach(target, at) {
846
+ function asAction(error, action) {
847
+ if (error instanceof Error) {
848
+ error.message = error.message.replace("locator.waitFor", `locator.${action}`);
849
+ error.stack &&= error.stack.replace("locator.waitFor", `locator.${action}`);
850
+ }
851
+ return error;
852
+ }
853
+ async function approach(target, at, timeout) {
854
+ await target.waitFor({
855
+ state: "attached",
856
+ timeout
857
+ });
748
858
  const scroll = await target.evaluate((element) => {
749
859
  const box = element.getBoundingClientRect();
750
860
  if (box.top >= 0 && box.bottom <= innerHeight) return void 0;
@@ -753,8 +863,7 @@ async function approach(target, at) {
753
863
  from: scrollY,
754
864
  to: Math.max(0, Math.min(most, scrollY + box.top + box.height / 2 - innerHeight / 2))
755
865
  };
756
- }, void 0, { timeout: 5e3 }).catch(() => "missing");
757
- if (scroll === "missing") return;
866
+ }, void 0, { timeout: 5e3 }).catch(() => void 0);
758
867
  if (scroll && Math.abs(scroll.to - scroll.from) >= 1) {
759
868
  const moves = Math.max(10, Math.min(36, Math.round(Math.abs(scroll.to - scroll.from) / 25)));
760
869
  for (let i = 1; i <= moves; i++) {
@@ -1,2 +1,4 @@
1
1
  import { type App } from "./repo.js";
2
2
  export declare function keepRunning(app: App, root: string): Promise<number | undefined>;
3
+ export declare function stopApp(url: string): Promise<void>;
4
+ export declare const serves: (url: string) => Promise<boolean>;
@@ -6,6 +6,7 @@ export type Found = {
6
6
  element?: Element;
7
7
  alike?: Element[];
8
8
  box?: Box;
9
+ pieces: string[];
9
10
  };
10
11
  export declare function changesAt(then: Reading, now: Reading, unsteady?: Set<string>, shown?: Shown): Found[];
11
12
  type Shown = {
@@ -9,5 +9,10 @@ export type Recorded = {
9
9
  picture: (name: string) => Promise<Buffer>;
10
10
  };
11
11
  export declare function compareWithEarlier(reviews: Reviews, id: string, number: number, flows: Flow[]): Promise<Comparison[]>;
12
- export declare function changesByThemselves(first: Recorded, second: Recorded): Promise<Change[]>;
13
- export declare function compare(page: Page, earlier: Recorded, round: Recorded, unsteady?: Set<string>): Promise<Comparison>;
12
+ export declare function changesByThemselves(first: Recorded, second: Recorded): Promise<(Change & {
13
+ pieces: string[];
14
+ })[]>;
15
+ export declare function compare(page: Page, earlier: Recorded, round: Recorded, unsteady?: Unsteady): Promise<Comparison>;
16
+ type Unsteady = (flow: string) => Set<string>;
17
+ export declare function unsteadyIn(flows: Flow[]): Unsteady;
18
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { type Did, type Flow } from "../../review/src/index.js";
2
- import { type Failed } from "./reporter.js";
2
+ import { type Failed, type Written } from "./reporter.js";
3
3
  import { type App, type Repo } from "./repo.js";
4
4
  export type Run = {
5
5
  flows: string;
@@ -14,6 +14,7 @@ export type Run = {
14
14
  taken: string[];
15
15
  started: string;
16
16
  walk?: Walk;
17
+ stops?: Record<string, string>;
17
18
  };
18
19
  export type Walk = {
19
20
  flow: string;
@@ -73,7 +74,9 @@ export type Cookie = {
73
74
  export declare const ALONGSIDE: unique symbol;
74
75
  export declare const REACHES_OUTSIDE = "reaches outside";
75
76
  export declare const BEAT = 650;
77
+ export declare const waitsFor: ({ before }: Pick<Run, "before">) => 3000 | 10000;
76
78
  export declare function makeFlowsFolder(repo: Repo, id: string): Promise<void>;
79
+ export declare const called: (names: string[]) => string;
77
80
  export declare const isFlow: (name: string) => boolean;
78
81
  export declare function howToWriteFlows(repo: Repo, id: string): string;
79
82
  export declare function runFlows(repo: Repo, id: string, round: number, { later, first }: {
@@ -86,15 +89,17 @@ export declare function runFlows(repo: Repo, id: string, round: number, { later,
86
89
  export declare function keepFailed(repo: Repo, id: string, round: number, failed: Failed[]): Promise<void>;
87
90
  export declare const failedLastTime: (repo: Repo, id: string) => Promise<string[]>;
88
91
  export declare const whereItStopped: (repo: Repo, id: string, failed: Failed[]) => string;
89
- type Ran = {
92
+ export type Ran = {
90
93
  recorded: {
91
94
  flow: Flow;
92
95
  files: {
93
96
  name: string;
94
97
  bytes: Buffer;
95
98
  }[];
99
+ wrote?: string;
96
100
  }[];
97
101
  failed: Failed[];
102
+ written: Record<string, Written>;
98
103
  };
99
104
  export type WalkedRun = Walked & {
100
105
  events: {
@@ -109,5 +114,4 @@ export type WalkedRun = Walked & {
109
114
  pictures: (Buffer | undefined)[];
110
115
  };
111
116
  export declare function walkFork(repo: Repo, id: string, walk: Walk): Promise<WalkedRun | undefined>;
112
- export declare const failedAt: ({ flow, at }: Failed) => string;
113
- export {};
117
+ export declare const failedAt: ({ flow, at, why }: Failed) => string;
@@ -1,6 +1,7 @@
1
1
  import type { Page } from "@playwright/test";
2
2
  import { type Reading } from "../../page/src/index.js";
3
3
  import { type Found } from "./changes.js";
4
+ export declare const HERE = "here:";
4
5
  export declare function picturesAt(page: Page, then: Reading, now: Reading, pictures: {
5
6
  then: Buffer;
6
7
  now: Buffer;
@@ -1,8 +1,9 @@
1
1
  import type { Page } from "@playwright/test";
2
- import type { Reading, Thing } from "../../page/src/index.js";
2
+ import type { Item, Reading, Thing } from "../../page/src/index.js";
3
3
  export declare function readPage(page: Page): Promise<{
4
4
  screen: Reading["screen"];
5
5
  things: Thing[];
6
+ items: Item[];
6
7
  }>;
7
8
  export declare function unhovered<T>(page: Page, look: () => Promise<T>): Promise<T>;
8
9
  export declare function read(): {
@@ -11,4 +12,5 @@ export declare function read(): {
11
12
  height: number;
12
13
  };
13
14
  things: any[];
15
+ items: Item[];
14
16
  };
@@ -2,6 +2,7 @@ import { Reviews } from "../../review/src/index.js";
2
2
  export type App = {
3
3
  start: string;
4
4
  url: string;
5
+ oneAtATime?: boolean;
5
6
  };
6
7
  export type Repo = ReturnType<typeof repo>;
7
8
  export declare function repo(root?: string): {
@@ -10,6 +11,7 @@ export declare function repo(root?: string): {
10
11
  flows: (review: string) => string;
11
12
  failed: (review: string) => string;
12
13
  round: (review: string, round: number) => string;
14
+ ranOnBefore: (review: string) => string;
13
15
  setAside: string;
14
16
  appFile: string;
15
17
  setup: string;
@@ -1,6 +1,6 @@
1
- import type { Reporter, TestCase, TestResult } from "@playwright/test/reporter";
1
+ import type { Reporter, TestCase, TestError, TestResult } from "@playwright/test/reporter";
2
2
  import type { Reading } from "../../page/src/index.js";
3
- import type { Flow, Reviews } from "../../review/src/index.js";
3
+ import { type Flow, type Reviews } from "../../review/src/index.js";
4
4
  export declare const RECORDING = "playthrough recording";
5
5
  export declare const READINGS = "playthrough readings";
6
6
  export declare const GLIMPSES = "playthrough glimpses";
@@ -9,6 +9,9 @@ export declare const ASSETS = "playthrough assets";
9
9
  export declare const PERSON = "playthrough person";
10
10
  export declare const HANDS = "playthrough hands";
11
11
  export declare const ACTIONS = "playthrough actions";
12
+ export declare const WROTE = "playthrough wrote";
13
+ export declare const UNMET = "playthrough unmet";
14
+ export declare const STOPPED_ON = "playthrough stopped on";
12
15
  export declare const HOLD = 1500;
13
16
  export declare const readingsOf: (flow: Flow) => string;
14
17
  export declare function readingsOn(reviews: Reviews, review: string, round: number, flow: Flow): Promise<Reading[]>;
@@ -19,23 +22,34 @@ export type Failed = {
19
22
  at?: string;
20
23
  why?: string;
21
24
  broken?: true;
25
+ on?: string;
22
26
  };
27
+ export declare const pathOf: (address: string) => string;
28
+ export declare const onPage: (on?: string) => string;
23
29
  export type Recorded = {
24
30
  flow: Flow;
25
31
  files: string[];
32
+ wrote?: string;
26
33
  };
27
34
  export type Hand = {
28
35
  at: number;
29
36
  person: string;
30
37
  };
38
+ export type Written = {
39
+ file: string;
40
+ hash: string;
41
+ };
42
+ export declare const hashOf: (file: string) => string | undefined;
31
43
  export default class Flows implements Reporter {
32
44
  #private;
33
- constructor({ folder, before, taken }: {
45
+ constructor({ folder, flows, before, taken }: {
34
46
  folder: string;
47
+ flows: string;
35
48
  before: boolean;
36
49
  taken: string[];
37
50
  });
38
- onTestEnd(test: TestCase, result: TestResult): undefined;
51
+ onTestEnd(test: TestCase, result: TestResult): void;
52
+ onError(error: TestError): void;
39
53
  onEnd(): void;
40
54
  printsToStdio(): boolean;
41
55
  }
@@ -1,4 +1,4 @@
1
- import { type APIRequestContext, type Page } from "@playwright/test";
1
+ import { type APIRequestContext, type BrowserContextOptions, type Page } from "@playwright/test";
2
2
  type Make = (given: {
3
3
  page: Page;
4
4
  request: APIRequestContext;
@@ -21,8 +21,11 @@ export type Setup = {
21
21
  after?: After;
22
22
  signIn?: SignIn;
23
23
  people?: People;
24
+ use?: BrowserContextOptions;
24
25
  };
25
26
  export declare function setupIn(file: string, written?: number): Promise<Setup>;
27
+ export declare const changes: (method: string) => boolean;
28
+ export declare function watching(request: APIRequestContext, changed: () => void): APIRequestContext;
26
29
  export declare const firstOf: ({ people }: Setup) => string;
27
30
  export declare function personIn({ people }: Setup, person: string): void;
28
31
  export type Made = {
@@ -8,7 +8,7 @@ export declare const tablet: Screen;
8
8
  export declare const expect: import("@playwright/test").Expect<{}>;
9
9
  export declare const onReviewDay: (started: string, now?: number) => Date;
10
10
  export type HandTo = (person: string, screen?: Screen) => Promise<Page>;
11
- export declare function pageFor(browser: Browser, baseURL: string, screen: Screen): Promise<{
11
+ export declare function pageFor(browser: Browser, baseURL: string, screen: Screen, use?: BrowserContextOptions): Promise<{
12
12
  context: BrowserContext;
13
13
  page: Page;
14
14
  }>;
@@ -1,5 +1,5 @@
1
- import { type Element, type Flow, type Place } from "../../review/src/index.js";
2
- import type { Reading, Thing } from "./reading.js";
1
+ import { type Element, type Flow, type Place, type Point } from "../../review/src/index.js";
2
+ import type { Glimpse, Reading, Thing } from "./reading.js";
3
3
  export type Recorded = {
4
4
  flow: Flow;
5
5
  readings: Reading[];
@@ -9,5 +9,7 @@ export type Shown = {
9
9
  thing: Thing;
10
10
  };
11
11
  export declare function placeIn(place: Place, from: Recorded, to: Recorded): Place;
12
+ export declare const HOLDING: RegExp;
13
+ export declare const nearTo: (read: Glimpse[], at: number, point: Point) => Thing | undefined;
12
14
  export declare function shownAt(readings: Reading[], element: Element, at: number): Shown | undefined;
13
15
  export declare const elementOf: (thing: Thing, name: string) => Element;
@@ -26,6 +26,16 @@ export type Thing = {
26
26
  labelled?: true;
27
27
  live?: true;
28
28
  node?: number;
29
+ item?: number;
30
+ };
31
+ export type Item = {
32
+ role: "row" | "listitem";
33
+ words: string;
34
+ box: Thing["box"];
35
+ page: Thing["page"];
36
+ list: number;
37
+ holder?: number;
38
+ node?: number;
29
39
  };
30
40
  export type Reading = {
31
41
  step: string;
@@ -36,5 +46,6 @@ export type Reading = {
36
46
  };
37
47
  picture: string;
38
48
  things: Thing[];
49
+ items?: Item[];
39
50
  };
40
51
  export type Glimpse = Omit<Reading, "picture">;
@@ -1,5 +1,6 @@
1
1
  import { Comparison, Round, Send, type Account, type Flow, type Note, type Place, type Review, type Verdict } from "./model.js";
2
2
  export declare function roundInProgress(review: Review): number;
3
+ export declare function inOrderJoined(review: Review): string[];
3
4
  export declare function allNotes(review: Review): Map<string, {
4
5
  note: Note;
5
6
  round: number;
@@ -24,8 +25,9 @@ export declare function problemsWithId(id: string): string[];
24
25
  export declare function problemsStarting(id: string, title: string, asked?: string): string[];
25
26
  export declare function problemsWithRound(review: Review, round: Round): string[];
26
27
  export declare function problemsAddingToBefore(review: Review, flows: Flow[], cantStart: string[]): string[];
27
- export declare function withInBefore(before: Round, flows: Flow[], cantStart: string[]): Round;
28
+ export declare function withInBefore(before: Round, flows: Flow[], cantStart: string[], gone?: string[]): Round;
28
29
  export declare function problemsWithComparison(review: Review, comparison: Comparison): string[];
30
+ export declare const LONGEST_LINE = 140;
29
31
  export declare function problemsFinishing(review: Review, number: number): string[];
30
32
  export declare function problemsWithFile(review: Review, round: number, name: string): string[];
31
33
  export declare function problemsWithFileName(name: string): string[];
@@ -29,10 +29,13 @@ export declare class Reviews {
29
29
  name: string;
30
30
  bytes: Uint8Array;
31
31
  }[];
32
+ commit?: string;
33
+ gone?: string[];
32
34
  }): Promise<void>;
33
35
  saveComparison(id: string, comparison: Comparison): Promise<void>;
34
36
  send(id: string, send: Send): Promise<void>;
35
37
  accept(id: string, by: string): Promise<void>;
36
38
  file(id: string, round: number, name: string): Promise<Uint8Array | undefined>;
39
+ files(id: string, round: number): Promise<string[]>;
37
40
  wait(id: string, until: (review: Review) => boolean, signal?: AbortSignal): Promise<Review>;
38
41
  }