playthrough 0.5.0 → 0.6.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/INSTRUCTIONS.md +35 -6
- package/README.md +3 -1
- package/package.json +2 -2
- package/src/{flows-DDB6AdQ-.js → flows-CbrwgoHB.js} +159 -24
- package/src/playthrough.js +228 -149
- package/src/playwright.config.js +1 -1
- package/src/{reporter-Dr7dWwSf.js → reporter-DlS7qehW.js} +7 -1
- package/src/reporter.js +1 -1
- package/src/test.js +113 -76
- package/types/cli/src/app.d.ts +3 -1
- package/types/cli/src/changes.d.ts +4 -0
- package/types/cli/src/command.d.ts +6 -0
- package/types/cli/src/flows.d.ts +21 -16
- package/types/cli/src/repo.d.ts +1 -0
- package/types/cli/src/test.d.ts +21 -8
- package/types/page/src/found.d.ts +5 -0
- package/types/page/src/index.d.ts +1 -0
- package/types/page/src/names.d.ts +1 -0
- package/types/page/src/scheme.d.ts +6 -0
- package/types/review/src/model.d.ts +46 -0
- package/types/review/src/rules.d.ts +6 -0
- package/viewer/assets/index-BbNKJRwK.css +2 -0
- package/viewer/assets/index-DwbSPQMk.js +194 -0
- package/viewer/index.html +2 -2
- package/viewer/assets/index-DVlUrKEE.js +0 -194
- package/viewer/assets/index-DhzHaV6E.css +0 -2
package/src/playwright.config.js
CHANGED
|
@@ -50,7 +50,11 @@ const Place = z.strictObject({
|
|
|
50
50
|
}),
|
|
51
51
|
did: z.array(Did).describe("What the person did there, in order"),
|
|
52
52
|
recording: fileName("needs its recording: the name of a file in the round").describe("The flow up to where it was taken over, then the person's own steps"),
|
|
53
|
-
length: z.number({ error: "needs its length, in seconds" }).positive({ error: "needs its length, in seconds" })
|
|
53
|
+
length: z.number({ error: "needs its length, in seconds" }).positive({ error: "needs its length, in seconds" }),
|
|
54
|
+
steps: z.array(z.strictObject({
|
|
55
|
+
title: text("needs its title"),
|
|
56
|
+
at: seconds$1("needs its start, in seconds")
|
|
57
|
+
})).optional().describe("Run again as the agent wrote it, a fork of its flow: its steps, each at its start")
|
|
54
58
|
}).optional().describe("In the app: on the person's own recording, which branches off a flow where they took it over. Its flow names the branch")
|
|
55
59
|
}).refine((place) => [
|
|
56
60
|
place.element,
|
|
@@ -76,6 +80,8 @@ const Flow = z.strictObject({
|
|
|
76
80
|
const Change = z.strictObject({
|
|
77
81
|
id: text("needs an id").describe("Its thing and its kind of change, so it keeps its id in every round while it's the same change"),
|
|
78
82
|
words: text("needs its words").describe("What changed, for the agent: “Download CSV and Cancel have swapped places”"),
|
|
83
|
+
reworded: text("needs the words that read differently").optional().describe("For words reworded, the words that read differently now, which the viewer marks where they are: “on their own”"),
|
|
84
|
+
gone: z.literal(true).optional().describe("Not on the newer round: where it was, on the round before, is where it's seen, with Compare"),
|
|
79
85
|
seen: z.array(Place, { error: "needs where it's seen, a list" }).min(1, { error: "needs where it's seen: each flow, at its moment there" }).describe("Each flow it's seen in, at its moment there; a flow that's gone, with no moment"),
|
|
80
86
|
pictureOnly: z.literal(true).optional().describe("Seen only in the pictures, with nothing read there changed: shown to the person, who can see what it is, and not put to the agent, who can't see more of it than Playthrough did")
|
|
81
87
|
});
|
package/src/reporter.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as ACTIONS, n as ASSETS, r as DONE_ON, a as GLIMPSES, o as HANDS, s as HOLD, c as PERSON, l as PICTURE, u as READINGS, d as RECORDING, f as WROTE, i as default, p as firstLine, m as glimpsesOf, h as readingsOf, g as readingsOn, _ as seconds } from "./reporter-
|
|
1
|
+
export { t as ACTIONS, n as ASSETS, r as DONE_ON, a as GLIMPSES, o as HANDS, s as HOLD, c as PERSON, l as PICTURE, u as READINGS, d as RECORDING, f as WROTE, i as default, p as firstLine, m as glimpsesOf, h as readingsOf, g as readingsOn, _ as seconds } from "./reporter-DlS7qehW.js";
|
package/src/test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as seconds, a as GLIMPSES, c as PERSON, d as RECORDING, f as WROTE, l as PICTURE, n as ASSETS, o as HANDS, p as firstLine, r as DONE_ON, s as HOLD, t as ACTIONS, u as READINGS, v as keepAssets } from "./reporter-
|
|
2
|
-
import { A as
|
|
1
|
+
import { _ as seconds, a as GLIMPSES, c as PERSON, d as RECORDING, f as WROTE, l as PICTURE, n as ASSETS, o as HANDS, p as firstLine, r as DONE_ON, s as HOLD, t as ACTIONS, u as READINGS, v as keepAssets } from "./reporter-DlS7qehW.js";
|
|
2
|
+
import { A as setupIn, E as firstOf, M as repo, O as make, T as changes, b as sameWords, k as personIn, n as FORK_RAN, r as REACHES_OUTSIDE, t as ALONGSIDE } from "./flows-CbrwgoHB.js";
|
|
3
3
|
import { n as readPage, r as unhovered } from "./read-Bg9XVTtn.js";
|
|
4
4
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { join } from "node:path";
|
|
@@ -92,15 +92,20 @@ function onlyWaits(expect) {
|
|
|
92
92
|
const recorder = (rrweb) => `;((Date, setTimeout, clearTimeout, requestAnimationFrame, cancelAnimationFrame) => {
|
|
93
93
|
${readFileSync(rrweb, "utf8")}
|
|
94
94
|
})(Date, setTimeout, clearTimeout, requestAnimationFrame, cancelAnimationFrame);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
// With each page taken whole, whether its browser was light or dark, still or moving, for it to be played back as it
|
|
96
|
+
// was: said once rrweb has the page, as said at its very start it went before anything was listening.
|
|
97
|
+
const said = () => window.rrwebRecord.record.addCustomEvent("browser", { dark: matchMedia("(prefers-color-scheme: dark)").matches, still: matchMedia("(prefers-reduced-motion: reduce)").matches });
|
|
98
|
+
if (window.top === window) {
|
|
99
|
+
window.rrwebRecord.record({
|
|
100
|
+
// Nothing is kept until the flow itself starts, after whatever the app's setup did on the page.
|
|
101
|
+
emit: (event) => (window.__playthrough?.(event), event.type === 2 && setTimeout(said)),
|
|
102
|
+
// every input, not just the last: with "last", rrweb only hears a field change when it loses focus,
|
|
103
|
+
// so words typed into a search box that kept focus never reached the recording
|
|
104
|
+
sampling: { mousemove: 20, scroll: 16, input: "all" },
|
|
105
|
+
maskAllInputs: false,
|
|
106
|
+
inlineStylesheet: true,
|
|
107
|
+
});
|
|
108
|
+
}`;
|
|
104
109
|
const FRAME = 16;
|
|
105
110
|
const REST = 140;
|
|
106
111
|
const KEY = 60;
|
|
@@ -133,7 +138,7 @@ const recorded = test$1.extend({
|
|
|
133
138
|
rest: async ({ page }, use) => use(run ? await watchRest(page) : async () => {}),
|
|
134
139
|
setup: async ({ page, request, baseURL, person, rest: _ }, use, testInfo) => {
|
|
135
140
|
if (run?.only && !run.only.includes(testInfo.title)) testInfo.skip();
|
|
136
|
-
if (run?.
|
|
141
|
+
if (run?.fork && testInfo.title !== run.fork.flow) testInfo.skip();
|
|
137
142
|
const app = await setupOf();
|
|
138
143
|
if (person) personIn(app, person);
|
|
139
144
|
if (run) await readied(page, run);
|
|
@@ -171,7 +176,7 @@ const recorded = test$1.extend({
|
|
|
171
176
|
});
|
|
172
177
|
for (const context of opened) await context.close();
|
|
173
178
|
},
|
|
174
|
-
recorder: [async ({ page, setup, rest, person, handTo
|
|
179
|
+
recorder: [async ({ page, request, baseURL, setup, rest, person, handTo }, use, testInfo) => {
|
|
175
180
|
if (!run) return use();
|
|
176
181
|
testInfo.setTimeout(testInfo.timeout * 3);
|
|
177
182
|
const flow = {
|
|
@@ -186,7 +191,15 @@ const recorded = test$1.extend({
|
|
|
186
191
|
inStep: 0,
|
|
187
192
|
hands: [],
|
|
188
193
|
assets: [],
|
|
189
|
-
doneOn: {}
|
|
194
|
+
doneOn: {},
|
|
195
|
+
given: {
|
|
196
|
+
page,
|
|
197
|
+
context: page.context(),
|
|
198
|
+
request,
|
|
199
|
+
baseURL,
|
|
200
|
+
setup,
|
|
201
|
+
handTo
|
|
202
|
+
}
|
|
190
203
|
};
|
|
191
204
|
await page.exposeFunction("__playthrough", (event) => void (flow.page === page && flow.events.push(event)));
|
|
192
205
|
flow.assets.push(keepAssets(page, run.folder));
|
|
@@ -209,15 +222,20 @@ const recorded = test$1.extend({
|
|
|
209
222
|
}
|
|
210
223
|
const last = flow.page;
|
|
211
224
|
const assets = async () => Object.assign({}, ...await Promise.all(flow.assets.map((kept) => kept())));
|
|
212
|
-
if (run.
|
|
213
|
-
if (!flow.
|
|
225
|
+
if (run.fork) {
|
|
226
|
+
if (!flow.forked) return;
|
|
214
227
|
await last.waitForTimeout(HOLD);
|
|
215
228
|
await last.evaluate(() => window.rrwebRecord.record.addCustomEvent("end", {}));
|
|
216
229
|
await last.waitForTimeout(100);
|
|
217
|
-
|
|
218
|
-
|
|
230
|
+
writeFileSync(join(run.folder, "end.png"), await last.screenshot({
|
|
231
|
+
animations: "disabled",
|
|
232
|
+
caret: "hide"
|
|
233
|
+
}));
|
|
234
|
+
return writeFileSync(join(run.folder, FORK_RAN), JSON.stringify({
|
|
235
|
+
...flow.forked,
|
|
219
236
|
events,
|
|
220
237
|
readings,
|
|
238
|
+
glimpses,
|
|
221
239
|
assets: await assets()
|
|
222
240
|
}));
|
|
223
241
|
}
|
|
@@ -382,12 +400,17 @@ function answers(dialog) {
|
|
|
382
400
|
}
|
|
383
401
|
}
|
|
384
402
|
let collecting;
|
|
403
|
+
let collectingForks = [];
|
|
385
404
|
let around = [];
|
|
386
405
|
let loading = Promise.resolve();
|
|
387
406
|
function flowsIn(file, written) {
|
|
388
407
|
const flows = /* @__PURE__ */ new Map();
|
|
389
408
|
const loaded = loading.then(async () => {
|
|
390
|
-
[collecting, around] = [
|
|
409
|
+
[collecting, around, collectingForks] = [
|
|
410
|
+
flows,
|
|
411
|
+
[{ use: {} }],
|
|
412
|
+
[]
|
|
413
|
+
];
|
|
391
414
|
try {
|
|
392
415
|
await import(`${pathToFileURL(file).href}?${written}`);
|
|
393
416
|
} finally {
|
|
@@ -401,7 +424,7 @@ function flowsIn(file, written) {
|
|
|
401
424
|
const taking = new AsyncLocalStorage();
|
|
402
425
|
const holds = (flow) => !flow.wrote && flow.page.url() === flow.address;
|
|
403
426
|
var Stopped = class extends Error {};
|
|
404
|
-
async function runAlongside(page, { request, baseURL, started, setup: appSetup, borrowed, arrived, person }, body, orders) {
|
|
427
|
+
async function runAlongside(page, { request, baseURL, started, setup: appSetup, borrowed, arrived, person }, body, orders, forks = []) {
|
|
405
428
|
const readied = async (page) => {
|
|
406
429
|
const rest = await watchRest(page);
|
|
407
430
|
await page.clock.setSystemTime(onReviewDay(started));
|
|
@@ -415,6 +438,7 @@ async function runAlongside(page, { request, baseURL, started, setup: appSetup,
|
|
|
415
438
|
};
|
|
416
439
|
const flow = {
|
|
417
440
|
page,
|
|
441
|
+
forks,
|
|
418
442
|
depth: 0,
|
|
419
443
|
rest: async () => {},
|
|
420
444
|
actions: 0,
|
|
@@ -463,14 +487,15 @@ async function runAlongside(page, { request, baseURL, started, setup: appSetup,
|
|
|
463
487
|
person: who,
|
|
464
488
|
setup: made.setup
|
|
465
489
|
});
|
|
466
|
-
|
|
490
|
+
flow.given = {
|
|
467
491
|
page,
|
|
468
492
|
context: page.context(),
|
|
469
493
|
request,
|
|
470
494
|
baseURL,
|
|
471
495
|
setup: made.setup,
|
|
472
496
|
handTo
|
|
473
|
-
}
|
|
497
|
+
};
|
|
498
|
+
await body(flow.given);
|
|
474
499
|
await reached(flow);
|
|
475
500
|
return {
|
|
476
501
|
handed: flow.handed,
|
|
@@ -500,7 +525,7 @@ async function reached(flow, step, actions) {
|
|
|
500
525
|
if (stopped) throw new Stopped();
|
|
501
526
|
const where = against(flow.passed, step, actions, to);
|
|
502
527
|
if (where === "on") return;
|
|
503
|
-
if (where === "here" && to.rests) await handOver(flow, to
|
|
528
|
+
if (where === "here" && to.rests) await handOver(flow, to);
|
|
504
529
|
if (where === "past" || to.rests) {
|
|
505
530
|
if (step) throw new Stopped();
|
|
506
531
|
return;
|
|
@@ -542,9 +567,13 @@ async function kept(page, rest) {
|
|
|
542
567
|
clock: kept.clock - Date.now()
|
|
543
568
|
};
|
|
544
569
|
}
|
|
545
|
-
async function handOver(flow, did) {
|
|
546
|
-
const
|
|
547
|
-
if (
|
|
570
|
+
async function handOver(flow, { notes, did }) {
|
|
571
|
+
const fork = notes && flow.forks.find((fork) => fork.notes.some((note) => notes.includes(note)));
|
|
572
|
+
if (fork) {
|
|
573
|
+
flow.forking = true;
|
|
574
|
+
await fork.body(flow.given);
|
|
575
|
+
} else if (did?.length) {
|
|
576
|
+
const { page, rest } = flow;
|
|
548
577
|
await taking.exit(async () => {
|
|
549
578
|
for (const one of did) {
|
|
550
579
|
if (!await doAgain(page, one).then(() => true, () => false)) break;
|
|
@@ -554,6 +583,7 @@ async function handOver(flow, did) {
|
|
|
554
583
|
flow.handed = await kept(page, rest);
|
|
555
584
|
return;
|
|
556
585
|
}
|
|
586
|
+
const { page, rest, from, again } = flow;
|
|
557
587
|
flow.handed = from && again.length && holds(flow) ? {
|
|
558
588
|
...from,
|
|
559
589
|
again
|
|
@@ -571,6 +601,7 @@ const pathTo = (target) => target.evaluate((element) => {
|
|
|
571
601
|
const playwrightStep = test$1.step;
|
|
572
602
|
const step = Object.assign(async (title, body, options) => {
|
|
573
603
|
const taken = taking.getStore();
|
|
604
|
+
if (taken?.forking) return body(void 0);
|
|
574
605
|
if (taken) {
|
|
575
606
|
const own = !taken.depth;
|
|
576
607
|
if (own) Object.assign(taken, {
|
|
@@ -597,6 +628,10 @@ const step = Object.assign(async (title, body, options) => {
|
|
|
597
628
|
step: title,
|
|
598
629
|
inStep: 0
|
|
599
630
|
});
|
|
631
|
+
flow.forked?.steps.push({
|
|
632
|
+
title,
|
|
633
|
+
at: seconds(Date.now() - (flow.events[0]?.timestamp ?? Date.now()))
|
|
634
|
+
});
|
|
600
635
|
flow.depth++;
|
|
601
636
|
let done;
|
|
602
637
|
try {
|
|
@@ -620,7 +655,7 @@ const step = Object.assign(async (title, body, options) => {
|
|
|
620
655
|
caret: "hide"
|
|
621
656
|
}));
|
|
622
657
|
});
|
|
623
|
-
if (run?.
|
|
658
|
+
if (run?.fork?.step === title && run.fork.actions === void 0 && !flow.forked) await forkOn(flow, run.fork);
|
|
624
659
|
return done;
|
|
625
660
|
}, options);
|
|
626
661
|
}, playwrightStep);
|
|
@@ -637,8 +672,10 @@ const outsideIn = (details) => {
|
|
|
637
672
|
};
|
|
638
673
|
const keeping = Object.assign((title, ...rest) => {
|
|
639
674
|
const scopes = [...around];
|
|
675
|
+
const forks = collectingForks;
|
|
640
676
|
collecting?.set(title, {
|
|
641
677
|
body: rest.at(-1),
|
|
678
|
+
forks,
|
|
642
679
|
outside: (rest.length > 1 ? outsideIn(rest[0]) : void 0) ?? scopes.findLast((scope) => scope.outside)?.outside,
|
|
643
680
|
get use() {
|
|
644
681
|
return Object.assign({}, ...scopes.map((scope) => scope.use));
|
|
@@ -660,7 +697,17 @@ const keeping = Object.assign((title, ...rest) => {
|
|
|
660
697
|
},
|
|
661
698
|
extend: () => keeping
|
|
662
699
|
});
|
|
663
|
-
const
|
|
700
|
+
const forks = [];
|
|
701
|
+
const fork = (name, { notes }, body) => void forks.push({
|
|
702
|
+
name,
|
|
703
|
+
notes: notes.map(String),
|
|
704
|
+
body
|
|
705
|
+
});
|
|
706
|
+
const test = globalThis[ALONGSIDE] ? Object.assign(keeping, { fork: (name, { notes }, body) => void collectingForks.push({
|
|
707
|
+
name,
|
|
708
|
+
notes: notes.map(String),
|
|
709
|
+
body
|
|
710
|
+
}) }) : Object.assign(withSteps(recorded), { fork });
|
|
664
711
|
const actions = [
|
|
665
712
|
"click",
|
|
666
713
|
"dblclick",
|
|
@@ -703,6 +750,20 @@ function pace(page) {
|
|
|
703
750
|
return result;
|
|
704
751
|
};
|
|
705
752
|
}
|
|
753
|
+
const mouse = Object.getPrototypeOf(page.mouse);
|
|
754
|
+
const wheel = mouse.wheel;
|
|
755
|
+
mouse.wheel = async function(deltaX, deltaY) {
|
|
756
|
+
if (!pointer) return wheel.call(this, deltaX, deltaY);
|
|
757
|
+
const moves = Math.max(10, Math.min(36, Math.round(Math.hypot(deltaX, deltaY) / 25)));
|
|
758
|
+
let done = 0;
|
|
759
|
+
for (let i = 1; i <= moves; i++) {
|
|
760
|
+
await wheel.call(this, deltaX * (ease(i / moves) - done), deltaY * (ease(i / moves) - done));
|
|
761
|
+
done = ease(i / moves);
|
|
762
|
+
await pointer.page.waitForTimeout(FRAME);
|
|
763
|
+
}
|
|
764
|
+
await pointer.page.waitForTimeout(650);
|
|
765
|
+
await glimpse();
|
|
766
|
+
};
|
|
706
767
|
const keyboard = Object.getPrototypeOf(page.keyboard);
|
|
707
768
|
const focused = async (page) => {
|
|
708
769
|
const on = page.locator(":focus").first();
|
|
@@ -721,7 +782,7 @@ function pace(page) {
|
|
|
721
782
|
}
|
|
722
783
|
async function acted(does, target, act, given) {
|
|
723
784
|
const taken = taking.getStore();
|
|
724
|
-
const keeping = taken?.depth ? taken : void 0;
|
|
785
|
+
const keeping = taken?.depth || taken?.forking ? taken : void 0;
|
|
725
786
|
let on, address;
|
|
726
787
|
if (keeping) {
|
|
727
788
|
if (!holds(keeping)) Object.assign(keeping, {
|
|
@@ -739,10 +800,10 @@ async function acted(does, target, act, given) {
|
|
|
739
800
|
await dialog;
|
|
740
801
|
});
|
|
741
802
|
underway?.actions.push(Date.now());
|
|
742
|
-
const
|
|
743
|
-
if (
|
|
803
|
+
const fork = run?.fork;
|
|
804
|
+
if (fork && underway?.depth && !underway.forked && fork.step === underway.step && fork.actions === ++underway.inStep) {
|
|
744
805
|
await underway.page.waitForTimeout(650);
|
|
745
|
-
await
|
|
806
|
+
await forkOn(underway, fork);
|
|
746
807
|
}
|
|
747
808
|
if (keeping) {
|
|
748
809
|
await keeping.page.waitForTimeout(SAVING);
|
|
@@ -757,7 +818,7 @@ async function acted(does, target, act, given) {
|
|
|
757
818
|
again: []
|
|
758
819
|
});
|
|
759
820
|
}
|
|
760
|
-
if (keeping) await reached(keeping, keeping.step, ++keeping.actions);
|
|
821
|
+
if (keeping && !keeping.forking) await reached(keeping, keeping.step, ++keeping.actions);
|
|
761
822
|
return result;
|
|
762
823
|
}
|
|
763
824
|
async function follow(flow, target) {
|
|
@@ -775,56 +836,32 @@ const doneOn = (target) => target.ariaSnapshot({ timeout: 1e3 }).then((snapshot)
|
|
|
775
836
|
} : void 0;
|
|
776
837
|
}, () => void 0);
|
|
777
838
|
function alike(one, other) {
|
|
778
|
-
|
|
779
|
-
const words = (name) => name.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
780
|
-
const [few, many] = [words(one.name), words(other.name)].sort((a, b) => a.length - b.length);
|
|
781
|
-
return !few.length || few.filter((word) => many.includes(word)).length * 2 >= few.length;
|
|
839
|
+
return one.role === other.role && sameWords(one.name, other.name);
|
|
782
840
|
}
|
|
783
|
-
async function
|
|
841
|
+
async function forkOn(flow, run) {
|
|
784
842
|
await flow.rest();
|
|
785
843
|
const since = () => seconds(Date.now() - (flow.events[0]?.timestamp ?? Date.now()));
|
|
786
|
-
const
|
|
844
|
+
const written = forks.find((fork) => fork.notes.some((note) => run.notes.includes(note)));
|
|
845
|
+
const ran = flow.forked = {
|
|
787
846
|
from: since(),
|
|
788
|
-
|
|
789
|
-
|
|
847
|
+
steps: [],
|
|
848
|
+
...written && { fork: written.name }
|
|
790
849
|
};
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
walked.found[i] = css ? await flow.page.evaluate((css) => {
|
|
795
|
-
const on = document.querySelector(css);
|
|
796
|
-
const node = on ? window.rrwebRecord.record.mirror.getId(on) : -1;
|
|
797
|
-
return node > 0 ? node : null;
|
|
798
|
-
}, css).catch(() => null) : null;
|
|
799
|
-
writeFileSync(join(run.folder, `look-${i}.png`), await flow.page.screenshot({
|
|
800
|
-
animations: "disabled",
|
|
801
|
-
caret: "hide"
|
|
802
|
-
}));
|
|
803
|
-
}
|
|
804
|
-
};
|
|
805
|
-
await look();
|
|
806
|
-
for (const did of walk.did) {
|
|
850
|
+
if (written) {
|
|
851
|
+
const depth = flow.depth;
|
|
852
|
+
flow.depth = 0;
|
|
807
853
|
try {
|
|
808
|
-
await
|
|
809
|
-
} catch {
|
|
810
|
-
|
|
811
|
-
|
|
854
|
+
await written.body(flow.given);
|
|
855
|
+
} catch (error) {
|
|
856
|
+
ran.failed = {
|
|
857
|
+
...ran.steps.length && { at: ran.steps.at(-1).title },
|
|
858
|
+
why: firstLine(String(error?.message ?? error))
|
|
859
|
+
};
|
|
860
|
+
} finally {
|
|
861
|
+
flow.depth = depth;
|
|
812
862
|
}
|
|
813
|
-
await flow.rest();
|
|
814
|
-
walked.done.push(since());
|
|
815
|
-
await unhovered(flow.page, async () => {
|
|
816
|
-
const { screen, things, items } = await readPage(flow.page);
|
|
817
|
-
flow.readings.push({
|
|
818
|
-
step: "In the app",
|
|
819
|
-
at: since(),
|
|
820
|
-
screen,
|
|
821
|
-
things,
|
|
822
|
-
items
|
|
823
|
-
});
|
|
824
|
-
});
|
|
825
|
-
await look();
|
|
826
863
|
}
|
|
827
|
-
test$1.info().skip(true, "
|
|
864
|
+
test$1.info().skip(true, "Ran as far as the note's fork");
|
|
828
865
|
}
|
|
829
866
|
async function doAgain(page, { does, on, with: given }) {
|
|
830
867
|
const target = page.locator(on).first();
|
|
@@ -843,7 +880,7 @@ async function doAgain(page, { does, on, with: given }) {
|
|
|
843
880
|
}
|
|
844
881
|
async function glimpse() {
|
|
845
882
|
const flow = underway;
|
|
846
|
-
if (!pointer || !flow || run?.
|
|
883
|
+
if (!pointer || !flow || run?.fork && !flow.forked) return;
|
|
847
884
|
const at = seconds(Date.now() - (flow.events[0]?.timestamp ?? Date.now()));
|
|
848
885
|
const read = await readPage(flow.page).catch(() => void 0);
|
|
849
886
|
if (read) flow.glimpses.push({
|
package/types/cli/src/app.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type App } from "./repo.js";
|
|
2
|
-
export declare function keepRunning(app: App, root: string
|
|
2
|
+
export declare function keepRunning(app: App, root: string, { ready }?: {
|
|
3
|
+
ready?: boolean | undefined;
|
|
4
|
+
}): Promise<number | undefined>;
|
|
3
5
|
export declare function stopApp(url: string): Promise<void>;
|
|
4
6
|
export declare const serves: (url: string) => Promise<boolean>;
|
|
@@ -7,6 +7,10 @@ export type Found = {
|
|
|
7
7
|
alike?: Element[];
|
|
8
8
|
box?: Box;
|
|
9
9
|
pieces: string[];
|
|
10
|
+
reworded?: string;
|
|
11
|
+
gone?: true;
|
|
12
|
+
offScreen?: true;
|
|
13
|
+
top?: number;
|
|
10
14
|
};
|
|
11
15
|
export declare function changesAt(then: Reading, now: Reading, unsteady?: Set<string>, shown?: Shown): Found[];
|
|
12
16
|
type Shown = {
|
package/types/cli/src/flows.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Glimpse, Reading } from "../../page/src/index.js";
|
|
1
2
|
import { type Did, type Flow } from "../../review/src/index.js";
|
|
2
3
|
import { type Failed, type Written } from "./reporter.js";
|
|
3
4
|
import { type App, type Repo } from "./repo.js";
|
|
@@ -14,7 +15,7 @@ export type Run = {
|
|
|
14
15
|
before: boolean;
|
|
15
16
|
taken: string[];
|
|
16
17
|
started: string;
|
|
17
|
-
|
|
18
|
+
fork?: ForkRun;
|
|
18
19
|
doneInRound?: Record<string, DoneOn>;
|
|
19
20
|
};
|
|
20
21
|
export type On = {
|
|
@@ -22,29 +23,32 @@ export type On = {
|
|
|
22
23
|
name: string;
|
|
23
24
|
};
|
|
24
25
|
export type DoneOn = Record<string, (On | null)[]>;
|
|
25
|
-
export type
|
|
26
|
+
export type ForkRun = {
|
|
26
27
|
flow: string;
|
|
27
28
|
step: string;
|
|
28
29
|
actions?: number;
|
|
29
|
-
|
|
30
|
-
look: {
|
|
31
|
-
after: number;
|
|
32
|
-
css?: string;
|
|
33
|
-
}[];
|
|
30
|
+
notes: string[];
|
|
34
31
|
};
|
|
35
|
-
export type
|
|
32
|
+
export type ForkRan = {
|
|
36
33
|
from: number;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
fork?: string;
|
|
35
|
+
steps: {
|
|
36
|
+
title: string;
|
|
37
|
+
at: number;
|
|
38
|
+
}[];
|
|
39
|
+
failed?: {
|
|
40
|
+
at?: string;
|
|
41
|
+
why: string;
|
|
42
|
+
};
|
|
40
43
|
};
|
|
41
|
-
export declare const
|
|
44
|
+
export declare const FORK_RAN = "fork.json";
|
|
42
45
|
export type To = {
|
|
43
46
|
flow: string;
|
|
44
47
|
step?: string;
|
|
45
48
|
actions?: number;
|
|
46
49
|
rests?: boolean;
|
|
47
50
|
did?: Did[];
|
|
51
|
+
notes?: string[];
|
|
48
52
|
};
|
|
49
53
|
export type Handed = {
|
|
50
54
|
url: string;
|
|
@@ -110,17 +114,18 @@ export type Ran = {
|
|
|
110
114
|
failed: Failed[];
|
|
111
115
|
written: Record<string, Written>;
|
|
112
116
|
};
|
|
113
|
-
export type
|
|
117
|
+
export type ForkRecorded = ForkRan & {
|
|
114
118
|
events: {
|
|
115
119
|
timestamp: number;
|
|
116
120
|
}[];
|
|
117
|
-
readings:
|
|
121
|
+
readings: Reading[];
|
|
122
|
+
glimpses: Glimpse[];
|
|
118
123
|
assets: Record<string, string>;
|
|
119
124
|
files: {
|
|
120
125
|
name: string;
|
|
121
126
|
bytes: Buffer;
|
|
122
127
|
}[];
|
|
123
|
-
|
|
128
|
+
picture?: Buffer;
|
|
124
129
|
};
|
|
125
|
-
export declare function
|
|
130
|
+
export declare function runFork(repo: Repo, id: string, fork: ForkRun): Promise<ForkRecorded | undefined>;
|
|
126
131
|
export declare const failedAt: ({ flow, at, why }: Failed) => string;
|
package/types/cli/src/repo.d.ts
CHANGED
package/types/cli/src/test.d.ts
CHANGED
|
@@ -8,6 +8,13 @@ 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
|
+
declare const recorded: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & {
|
|
12
|
+
person: string | undefined;
|
|
13
|
+
rest: () => Promise<void>;
|
|
14
|
+
setup: Record<string, any>;
|
|
15
|
+
handTo: HandTo;
|
|
16
|
+
recorder: void;
|
|
17
|
+
}, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
|
11
18
|
export declare function pageFor(browser: Browser, baseURL: string, screen: Screen, use?: BrowserContextOptions): Promise<{
|
|
12
19
|
context: BrowserContext;
|
|
13
20
|
page: Page;
|
|
@@ -31,6 +38,7 @@ export type Kept = {
|
|
|
31
38
|
use: BrowserContextOptions & {
|
|
32
39
|
person?: string;
|
|
33
40
|
};
|
|
41
|
+
forks: Forked[];
|
|
34
42
|
};
|
|
35
43
|
export declare function flowsIn(file: string, written: number): Promise<Map<string, Kept>>;
|
|
36
44
|
export type Arrived = (step: string | undefined, actions: number | undefined, worked: number) => void;
|
|
@@ -42,15 +50,20 @@ export declare function runAlongside(page: Page, { request, baseURL, started, se
|
|
|
42
50
|
borrowed: string;
|
|
43
51
|
arrived?: Arrived;
|
|
44
52
|
person?: string;
|
|
45
|
-
}, body: Body, orders: Orders): Promise<{
|
|
53
|
+
}, body: Body, orders: Orders, forks?: Forked[]): Promise<{
|
|
46
54
|
handed?: Handed;
|
|
47
55
|
stopped?: Pick<Failed, "at" | "why">;
|
|
48
56
|
made?: Made;
|
|
49
57
|
}>;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
type Forked = {
|
|
59
|
+
name: string;
|
|
60
|
+
notes: string[];
|
|
61
|
+
body: Body;
|
|
62
|
+
};
|
|
63
|
+
declare const fork: (name: string, { notes }: {
|
|
64
|
+
notes: string[];
|
|
65
|
+
}, body: Body) => undefined;
|
|
66
|
+
export declare const test: typeof recorded & {
|
|
67
|
+
fork: typeof fork;
|
|
68
|
+
};
|
|
69
|
+
export {};
|
|
@@ -13,3 +13,8 @@ export declare const HOLDING: RegExp;
|
|
|
13
13
|
export declare const nearTo: (read: Glimpse[], at: number, point: Point) => Thing | undefined;
|
|
14
14
|
export declare function shownAt(readings: Reading[], element: Element, at: number): Shown | undefined;
|
|
15
15
|
export declare const elementOf: (thing: Thing, name: string) => Element;
|
|
16
|
+
export declare function sameWords(one: string, other: string): boolean;
|
|
17
|
+
export declare function firstShown(element: Element, seen: Glimpse[]): {
|
|
18
|
+
at: number;
|
|
19
|
+
element: Element;
|
|
20
|
+
} | undefined;
|
|
@@ -13,4 +13,5 @@ export declare const sentence: (words: string) => string;
|
|
|
13
13
|
export declare const wayOf: (across: number, down: number) => string;
|
|
14
14
|
export declare function together(all: Thing[], name?: (thing: Thing) => string): string;
|
|
15
15
|
export declare function rewordedWords(a: Thing, b: Thing): string;
|
|
16
|
+
export declare function rewordedPart(a: string, b: string): string | undefined;
|
|
16
17
|
export declare function lookWords(a: Thing, b: Thing): string;
|