playthrough 0.1.0 → 0.3.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 +60 -64
- package/package.json +1 -1
- package/src/{read-BBNVC-oH.js → flows-Dt7fcPLt.js} +647 -1098
- package/src/playthrough.js +246 -122
- package/src/playwright.config.js +6 -10
- package/src/read-Bg9XVTtn.js +278 -0
- package/src/reporter-4C_7joIm.js +919 -0
- package/src/reporter.js +1 -1
- package/src/test.js +121 -16
- package/types/cli/src/app.d.ts +4 -0
- package/types/cli/src/changes.d.ts +1 -0
- package/types/cli/src/compare.d.ts +7 -2
- package/types/cli/src/flows.d.ts +8 -4
- package/types/cli/src/pictures.d.ts +1 -0
- package/types/cli/src/read.d.ts +3 -1
- package/types/cli/src/repo.d.ts +4 -0
- package/types/cli/src/reporter.d.ts +15 -5
- package/types/cli/src/setup.d.ts +5 -2
- package/types/cli/src/test.d.ts +1 -1
- package/types/cli/src/try-setup.d.ts +2 -0
- package/types/page/src/reading.d.ts +11 -0
- package/types/review/src/rules.d.ts +2 -1
- package/types/review/src/store.d.ts +3 -0
- package/viewer/assets/index-Bbi9sjtm.js +193 -0
- package/viewer/assets/index-gTfnUrcK.css +2 -0
- package/viewer/index.html +2 -2
- package/src/reporter-Biym3TnN.js +0 -197
- package/viewer/assets/index-BriVL50M.js +0 -193
- package/viewer/assets/index-DaS2F2-s.css +0 -2
package/INSTRUCTIONS.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
Playthrough: a person reviews your work on a web app by watching recordings of it, round after round. Read this once
|
|
2
|
-
command then says what's next.
|
|
1
|
+
Playthrough: a person reviews your work on a web app by watching recordings of it, round after round. Read this once,
|
|
2
|
+
to its end; each command then says what's next.
|
|
3
3
|
|
|
4
4
|
playthrough start <review> "<title>" "<what was asked>"
|
|
5
5
|
start a review: a title for the change, and what was asked, in the person's words
|
|
@@ -11,24 +11,22 @@ command then says what's next.
|
|
|
11
11
|
|
|
12
12
|
THE LOOP
|
|
13
13
|
|
|
14
|
-
1. Start a review with a title,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
1. Start a review with a title, as you'd title its pull request, and what was asked, word for word: it's what the person
|
|
15
|
+
judges each round against. Before you change anything, write its flows, one for each thing a person does that the
|
|
16
|
+
task touches, from the task, not from the code.
|
|
17
|
+
2. Run playthrough start <review> again. It records Before: each flow on the app as it is, as far as it can go. There,
|
|
18
|
+
what a flow expects of the finished work only waits a moment; the flow stops only at a step the app can't do yet.
|
|
19
19
|
3. Change the app. Keep each flow's name, and each step's while it does the same job: they're matched across rounds by
|
|
20
20
|
name. Once the work is done, add a flow for each choice you made that the person can see and didn't ask for.
|
|
21
|
-
4. playthrough record <review> records the round and compares it with every round before it. In the round's round.json,
|
|
22
|
-
why each change since the round before is there, under "accounts" by its id: { "line": "<why, in your words>" },
|
|
21
|
+
4. playthrough record <review> records the round and compares it with every round before it. In the round's round.json,
|
|
22
|
+
say why each change since the round before is there, under "accounts" by its id: { "line": "<why, in your words>" },
|
|
23
23
|
{ "note": "<the note it was made for>" }, or { "notReal": "<why nothing really changed>" }.
|
|
24
|
-
5. playthrough wait <review> hands the round over and waits for the notes, which can take hours: run it in the
|
|
25
|
-
so you're told when it ends.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
there: answer it in words, not with a new flow, and check your fix with playthrough walk <review> while you work. Where
|
|
31
|
-
their steps stop short, fix that, or say in your answer why they changed.
|
|
24
|
+
5. playthrough wait <review> hands the round over and waits for the notes, which can take hours: run it in the
|
|
25
|
+
background, so you're told when it ends. Each note comes with a picture of what it's on, or what the person did in
|
|
26
|
+
the app. Change the app, record the next round, and answer each note in its round.json, under "replies". Where the
|
|
27
|
+
answer moves, like an animation, add "watch": "<the title of the step it's seen in>". For a note made in the app,
|
|
28
|
+
record walks the person's steps again and puts your answer where they lead: answer it in words, not with a new flow,
|
|
29
|
+
and check your fix with playthrough walk <review>. Where their steps stop short, fix that, or say why they changed.
|
|
32
30
|
|
|
33
31
|
The person can only judge what the flows show, so keep them true to how a person uses the app.
|
|
34
32
|
|
|
@@ -38,14 +36,17 @@ playthrough/app.json says how to start the app, and is committed so every review
|
|
|
38
36
|
|
|
39
37
|
{ "start": "npm run dev", "url": "http://localhost:3000" }
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
Flows run several at once. Where a page shows what every run made, as a blog's index lists every post, add
|
|
40
|
+
"oneAtATime": true. Every round is recorded on whatever serves that url, and Playthrough starts the app only when nothing
|
|
41
|
+
does: leave a server you didn't start running, and let it follow the working copy, as a dev server does. A flow recorded
|
|
42
|
+
on Before later sets your work aside with git stash for a minute, puts the app as it was in its place, with playthrough/
|
|
43
|
+
as it is now, and puts your work back after, even when Playthrough is stopped. Until round 1 is handed over, Before
|
|
44
|
+
follows what you fix: a flow whose file changes is recorded on it again, and so is every flow once playthrough/ changes.
|
|
45
45
|
|
|
46
46
|
FLOWS
|
|
47
47
|
|
|
48
|
-
Each flow is a Playwright test in .playthrough/<review>/flows/, with a step for each part of it. The test's
|
|
48
|
+
Each flow is a Playwright test in a .ts file in .playthrough/<review>/flows/, with a step for each part of it. The test's
|
|
49
|
+
name is the flow's:
|
|
49
50
|
|
|
50
51
|
import { test, expect } from "playthrough/test";
|
|
51
52
|
|
|
@@ -59,79 +60,74 @@ Each flow is a Playwright test in .playthrough/<review>/flows/, with a step for
|
|
|
59
60
|
});
|
|
60
61
|
});
|
|
61
62
|
|
|
62
|
-
Act on the page as a person would, by role and label. Playthrough glides the pointer, types
|
|
63
|
-
|
|
63
|
+
Act on the page as a person would, by role and label. Playthrough glides the pointer, types a key at a time, and reads
|
|
64
|
+
the page once it rests after each step. A dialog the browser shows is recorded, and answered No unless the flow says Yes
|
|
65
|
+
first: page.once("dialog", (dialog) => dialog.accept()). A flow that fails stops the round: record names it and the
|
|
66
|
+
step, and keeps the page it failed on in .playthrough/<review>/failed/, as Before does where each flow stops.
|
|
64
67
|
|
|
65
|
-
While the
|
|
66
|
-
|
|
67
|
-
email, charging a card, calling a paid API or posting where others see it, say so, and it's run only when taken over:
|
|
68
|
+
While the person watches, Playthrough runs the flow again in the background, so taking it over is quick. If its steps do
|
|
69
|
+
something outside the app that mustn't happen again, like sending an email, say so, and it runs only when taken over:
|
|
68
70
|
|
|
69
71
|
test("Invite Sam", { annotation: { type: "reaches outside", description: "sends Sam an email" } }, async ({ page }) => {
|
|
70
72
|
|
|
71
|
-
A flow that fails stops the round: record names it and the step it failed at, and keeps what the page held there, with
|
|
72
|
-
a picture, in .playthrough/<review>/failed/. Before keeps the page each flow stops on there too. Recording again runs the
|
|
73
|
-
flows that failed first, and stops there if one fails again, without waiting for the rest.
|
|
74
|
-
|
|
75
73
|
WHAT EVERY FLOW STARTS FROM
|
|
76
74
|
|
|
77
|
-
A flow has to start from the same place every round, or the comparison finds changes nobody made. Playthrough starts
|
|
78
|
-
|
|
79
|
-
from a fixed seed
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
A flow has to start from the same place every round, or the comparison finds changes nobody made. Playthrough starts
|
|
76
|
+
each on the day the review started, at the time of day it's recorded, in English and UTC, with the page's random numbers
|
|
77
|
+
from a fixed seed. The rest is made in playthrough/setup.ts, committed. Its default export runs before each flow, given
|
|
78
|
+
the page, Playwright's request and the app's address as baseURL, and returns the flow's setup; after, if exported, gives
|
|
79
|
+
back what was made once the run is over; use, if exported, opens every page as test.use would, like a user agent the app
|
|
80
|
+
tells tests by (the screen, language and time zone are Playthrough's):
|
|
82
81
|
|
|
83
82
|
import type { APIRequestContext, Page } from "@playwright/test";
|
|
84
83
|
|
|
84
|
+
export const use = { userAgent: "playwright" };
|
|
85
|
+
|
|
85
86
|
export default async function ({ page, request }: { page: Page; request: APIRequestContext; baseURL?: string }) {
|
|
86
87
|
const account = await (await request.post("/api/test/accounts", { data: { name: "Lake trip" } })).json();
|
|
87
88
|
await page.route("https://api.exchangerate.host/**", (route) => route.fulfill({ json: { rate: 1.1 } }));
|
|
88
|
-
await page.addInitScript(() => localStorage.setItem("signedIn", "ana"));
|
|
89
89
|
return { account: account.id };
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
export async function after({ request, setup }: { request: APIRequestContext; baseURL: string; setup: Record<string, any> }) {
|
|
93
|
-
await request.delete(`/api/test/accounts/${setup.account}`);
|
|
93
|
+
const removed = await request.delete(`/api/test/accounts/${setup.account}`);
|
|
94
|
+
if (!removed.ok()) throw new Error(`Removing the account failed: ${removed.status()}`);
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
Each run makes its own
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
Each run makes its own: what the app needs unique, like an email, of its own, and what the page shows, like a name, the
|
|
98
|
+
same every run. Check what each request answers, so one that fails says so. Avoid what can't be given back, like an
|
|
99
|
+
email sent: test keys, a mail catcher. Use what the project has (a test account, seed data, its own fixtures), make data
|
|
100
|
+
through the API or database rather than on screen, and stub what you can't control at the network. Sign in with
|
|
101
|
+
page.request, not request: it shares the page's cookies, so a person who takes the flow over is signed in too. Dates
|
|
102
|
+
the server works out don't follow the page's clock: fix them through the app's own test hook if it has one.
|
|
101
103
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
has one.
|
|
107
|
-
|
|
108
|
-
Each new flow is recorded twice. What changes by itself between the two, like a time the server stamps, is left out of
|
|
109
|
-
every comparison, in every flow, and Playthrough says what it is: steady it in playthrough/setup.ts if you can, because a real
|
|
110
|
-
change to it won't be seen.
|
|
104
|
+
Whenever app.json or setup.ts changes, Playthrough tries the setup by itself first, twice for each of the app's people,
|
|
105
|
+
and shows a picture of where each starts. Fix what the pictures show and run the command again. It's the person's app:
|
|
106
|
+
tell them how flows get into it, and ask before you change it for Playthrough's sake, without waiting for their answer.
|
|
107
|
+
Each new flow is recorded twice: what changes by itself, like a time the server stamps, is left out of every comparison.
|
|
111
108
|
|
|
112
109
|
WHO DOES A FLOW
|
|
113
110
|
|
|
114
|
-
Every flow is done by someone
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
one. Add one the first time a change needs them, never all at once:
|
|
111
|
+
Every flow is done by someone: their name is on the pointer the person watches, and taking the flow over, the person is
|
|
112
|
+
them. Once the app has more than one kind of person, name them in setup.ts as the team does, each with a line for you;
|
|
113
|
+
the first does every flow that names no one. Add one when a change first needs them:
|
|
118
114
|
|
|
119
115
|
export const people = {
|
|
120
116
|
"the owner": "Runs the studio: sends the invoices. On a laptop.",
|
|
121
117
|
"a customer": "Pays from the link in a reminder, on their phone.",
|
|
122
118
|
};
|
|
123
119
|
|
|
124
|
-
Setup's default export is told who the flow is done as (person),
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
file, with their screen when it isn't a laptop's (phone and tablet come from playthrough/test):
|
|
120
|
+
Setup's default export is told who the flow is done as (person); signIn, if exported, signs a page in as one of them:
|
|
121
|
+
({ page, person, setup }) => …. A flow names who it's done as at the top of its file, with their screen when it isn't a
|
|
122
|
+
laptop's (phone and tablet come from playthrough/test):
|
|
128
123
|
|
|
129
124
|
test.use({ person: "a customer", ...phone });
|
|
130
125
|
|
|
131
|
-
Another person earns a flow only when they'd see something different,
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
carries on there, so what lands on their page is what the first person really did.
|
|
126
|
+
Another person earns a flow only when they'd see something different, named for it: "A member can't delete a board".
|
|
127
|
+
When the task passes from one person to another, hand the flow on: they carry on on their own page in the same world,
|
|
128
|
+
and the recording follows, so what lands on their page is what the first person really did.
|
|
135
129
|
|
|
136
130
|
const customer = await handTo("a customer", phone);
|
|
137
131
|
await test.step("Open the link in the reminder", () => customer.goto(link));
|
|
132
|
+
|
|
133
|
+
That's all. Start with playthrough start <review> "<title>" "<what was asked>".
|