@jskit-ai/agent-docs 0.1.84 → 0.1.85
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/guide/agent/app-setup/authentication.md +53 -80
- package/guide/agent/app-setup/initial-scaffolding.md +2 -0
- package/guide/agent/app-setup/working-with-the-jskit-cli.md +2 -0
- package/package.json +1 -1
- package/patterns/ui-testing.md +68 -60
- package/reference/autogen/tooling/create-app.md +8 -0
|
@@ -961,39 +961,40 @@ That explains why the login screen and auth guard runtime both care about `/api/
|
|
|
961
961
|
|
|
962
962
|
It is also why the shell widget can react cleanly to auth state without storing raw session tokens in client state. The browser just asks the app for the current session view, and the app derives that from its cookies plus the selected provider.
|
|
963
963
|
|
|
964
|
-
### Authenticated Playwright testing
|
|
964
|
+
### Authenticated Playwright testing
|
|
965
965
|
|
|
966
|
-
JSKIT
|
|
966
|
+
JSKIT supports two intentionally separate authenticated-browser paths:
|
|
967
967
|
|
|
968
|
-
|
|
968
|
+
- a private localhost exchange for direct local Playwright runs
|
|
969
|
+
- runner-provided Playwright storage state for managed previews
|
|
969
970
|
|
|
970
|
-
-
|
|
971
|
-
- create a session for an existing user through the local app
|
|
972
|
-
- let the browser keep the resulting HTTP-only cookies
|
|
973
|
-
- navigate to the protected page and verify the feature
|
|
974
|
-
- record the Playwright run through `jskit app verify-ui` so `jskit doctor` can verify the receipt later
|
|
971
|
+
Neither path drives a live external login provider. Both put the resulting HTTP-only cookies into the same Playwright browser context that runs the feature assertions.
|
|
975
972
|
|
|
976
|
-
The
|
|
973
|
+
The generated `playwright.config.mjs` is a thin delegate to `@jskit-ai/jskit-cli/test/playwright`. The published helper owns the changing setup behavior:
|
|
977
974
|
|
|
978
|
-
-
|
|
979
|
-
-
|
|
980
|
-
-
|
|
981
|
-
- The route only looks up an existing user. It does not create one.
|
|
975
|
+
- without `PLAYWRIGHT_BASE_URL`, it builds the app and starts the local server on `http://127.0.0.1:4173`
|
|
976
|
+
- with `PLAYWRIGHT_BASE_URL`, it uses that managed preview and does not start another server
|
|
977
|
+
- with `JSKIT_PLAYWRIGHT_STORAGE_STATE`, it loads the supplied authenticated state into Playwright contexts
|
|
982
978
|
|
|
983
|
-
|
|
979
|
+
Tests should therefore navigate with relative paths such as `page.goto("/w/acme/admin/contacts")`.
|
|
980
|
+
|
|
981
|
+
#### Direct local dev-auth login
|
|
982
|
+
|
|
983
|
+
The local dev auth bypass remains deliberately narrow:
|
|
984
|
+
|
|
985
|
+
- it is available only outside production
|
|
986
|
+
- the request must arrive locally
|
|
987
|
+
- it selects an existing user and never creates one
|
|
988
|
+
- it requires both CSRF protection and a private exchange secret
|
|
989
|
+
|
|
990
|
+
Enable it for the Playwright process and the local server it starts:
|
|
984
991
|
|
|
985
992
|
```bash
|
|
986
993
|
AUTH_DEV_BYPASS_ENABLED=true
|
|
987
994
|
AUTH_DEV_BYPASS_SECRET=replace-this-with-a-local-dev-secret
|
|
988
995
|
```
|
|
989
996
|
|
|
990
|
-
When enabled
|
|
991
|
-
|
|
992
|
-
```text
|
|
993
|
-
POST /api/dev-auth/login-as
|
|
994
|
-
```
|
|
995
|
-
|
|
996
|
-
The request body must contain either:
|
|
997
|
+
When enabled, the app exposes `POST /api/dev-auth/login-as`. The body contains exactly one existing-user identity:
|
|
997
998
|
|
|
998
999
|
```json
|
|
999
1000
|
{ "userId": "7" }
|
|
@@ -1005,70 +1006,45 @@ or:
|
|
|
1005
1006
|
{ "email": "ada@example.com" }
|
|
1006
1007
|
```
|
|
1007
1008
|
|
|
1008
|
-
The
|
|
1009
|
+
Do not call that route from `page.evaluate()`. The route requires the private `x-jskit-dev-auth-secret` header as well as the CSRF token. Putting the secret into browser JavaScript, client-visible environment, query parameters, or page globals would defeat the exchange boundary.
|
|
1009
1010
|
|
|
1010
|
-
|
|
1011
|
-
{
|
|
1012
|
-
"ok": true,
|
|
1013
|
-
"userId": "7",
|
|
1014
|
-
"username": "Ada Example",
|
|
1015
|
-
"email": "ada@example.com"
|
|
1016
|
-
}
|
|
1017
|
-
```
|
|
1011
|
+
Use the published Node-side helper instead:
|
|
1018
1012
|
|
|
1019
|
-
|
|
1013
|
+
```ts
|
|
1014
|
+
import { expect, test } from "@playwright/test";
|
|
1015
|
+
import { loginAsExistingUser } from "@jskit-ai/auth-web/test/playwright";
|
|
1020
1016
|
|
|
1021
|
-
|
|
1017
|
+
test("authenticated contacts filters", async ({ page }) => {
|
|
1018
|
+
await loginAsExistingUser(page, { email: "ada@example.com" });
|
|
1022
1019
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1020
|
+
await page.goto("/w/acme/admin/contacts");
|
|
1021
|
+
await expect(page.getByRole("heading", { name: "Contacts" })).toBeVisible();
|
|
1022
|
+
});
|
|
1023
|
+
```
|
|
1026
1024
|
|
|
1027
|
-
|
|
1025
|
+
`loginAsExistingUser()` runs from the Playwright Node process. It uses the request client attached to `page.context()`, reads `csrfToken` from `GET /api/session`, sends that token plus `x-jskit-dev-auth-secret` to the login route, and leaves the response cookies in the same browser context. It reads `AUTH_DEV_BYPASS_SECRET` from the Node process by default and refuses to send it to a non-local URL.
|
|
1028
1026
|
|
|
1029
|
-
|
|
1030
|
-
2. call `/api/session` to read `csrfToken`
|
|
1031
|
-
3. call `/api/dev-auth/login-as` with `credentials: "include"` and the `csrf-token` header
|
|
1032
|
-
4. navigate to the protected route and run the assertions
|
|
1027
|
+
That last restriction is important. A managed preview URL is not a direct localhost app connection, even when its backend eventually runs on the same infrastructure. Project code must not receive the managed host's private exchange authority.
|
|
1033
1028
|
|
|
1034
|
-
|
|
1029
|
+
#### Managed preview authentication
|
|
1035
1030
|
|
|
1036
|
-
|
|
1037
|
-
await page.goto("/");
|
|
1031
|
+
A managed host authenticates outside the project browser context. It performs its trusted identity exchange, writes the resulting cookies and origins to a temporary Playwright storage-state file, and starts the project test with:
|
|
1038
1032
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
throw new Error(`Session bootstrap failed: ${sessionResponse.status}`);
|
|
1045
|
-
}
|
|
1033
|
+
```bash
|
|
1034
|
+
PLAYWRIGHT_BASE_URL=https://managed-preview.example.test \
|
|
1035
|
+
JSKIT_PLAYWRIGHT_STORAGE_STATE=/secure/temp/playwright-state.json \
|
|
1036
|
+
playwright test tests/e2e/contacts.spec.ts
|
|
1037
|
+
```
|
|
1046
1038
|
|
|
1047
|
-
|
|
1048
|
-
const csrfToken = String(sessionPayload?.csrfToken || "");
|
|
1049
|
-
if (!csrfToken) {
|
|
1050
|
-
throw new Error("Missing csrfToken from /api/session.");
|
|
1051
|
-
}
|
|
1039
|
+
JSKIT only consumes that runner-neutral contract. It does not detect a particular host, call host-specific commands, proxy private headers, or install a browser.
|
|
1052
1040
|
|
|
1053
|
-
|
|
1054
|
-
method: "POST",
|
|
1055
|
-
credentials: "include",
|
|
1056
|
-
headers: {
|
|
1057
|
-
"content-type": "application/json",
|
|
1058
|
-
"csrf-token": csrfToken
|
|
1059
|
-
},
|
|
1060
|
-
body: JSON.stringify({ email })
|
|
1061
|
-
});
|
|
1041
|
+
The storage-state file is a secret because it can contain authenticated cookies. The managed runner must create it with appropriately restricted access, must not print or commit it, and must delete it after the run.
|
|
1062
1042
|
|
|
1063
|
-
|
|
1064
|
-
throw new Error(`Dev login failed: ${loginResponse.status} ${await loginResponse.text()}`);
|
|
1065
|
-
}
|
|
1066
|
-
}, { email: "ada@example.com" });
|
|
1043
|
+
Tests using managed state do not call `loginAsExistingUser()`. They begin with the runner-provided identity already present and navigate using relative paths. An ordinary browser or proxy request to `/api/dev-auth/login-as` without the private header must continue to fail with `403 Dev auth exchange is not authorized.`
|
|
1067
1044
|
|
|
1068
|
-
|
|
1069
|
-
```
|
|
1045
|
+
#### Recording the result
|
|
1070
1046
|
|
|
1071
|
-
|
|
1047
|
+
After the actual Playwright flow succeeds, record it through JSKIT:
|
|
1072
1048
|
|
|
1073
1049
|
```bash
|
|
1074
1050
|
npx jskit app verify-ui \
|
|
@@ -1077,20 +1053,17 @@ npx jskit app verify-ui \
|
|
|
1077
1053
|
--auth-mode dev-auth-login-as
|
|
1078
1054
|
```
|
|
1079
1055
|
|
|
1080
|
-
|
|
1056
|
+
Use `--auth-mode session-bootstrap` when a managed runner supplied authenticated storage state.
|
|
1057
|
+
|
|
1058
|
+
`jskit app verify-ui` executes the command and records the command, auth-mode label, feature, and changed UI files. The `--auth-mode` value describes how the Playwright command was authenticated. It does not create a session, inject a secret, or alter the browser context.
|
|
1059
|
+
|
|
1060
|
+
For local pre-merge review, follow the recorded run with:
|
|
1081
1061
|
|
|
1082
1062
|
```bash
|
|
1083
1063
|
npx jskit doctor --against origin/main
|
|
1084
1064
|
```
|
|
1085
1065
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
That flow is preferable to driving the real sign-in form in feature tests because it keeps the test focused on the UI feature being added, not on an external auth dependency. If a chunk changes user-facing UI and the flow requires login, the expected JSKIT review standard is:
|
|
1089
|
-
|
|
1090
|
-
- use Playwright
|
|
1091
|
-
- record the run with `jskit app verify-ui`
|
|
1092
|
-
- use the local dev auth bypass or another local session bootstrap path
|
|
1093
|
-
- exercise the actual changed behavior, not only page load
|
|
1066
|
+
This keeps feature tests focused on the changed UI while preserving the security boundary around session creation.
|
|
1094
1067
|
|
|
1095
1068
|
## When you later switch to Supabase
|
|
1096
1069
|
|
|
@@ -210,6 +210,8 @@ Published JSKIT libraries and tooling support Node.js 22 from 22.12.0 onward, No
|
|
|
210
210
|
|
|
211
211
|
That matters because JSKIT maintenance policy changes over time. If the scaffold copied a large shell script into every app, existing apps would freeze the old behavior forever. By delegating to `jskit app verify`, `jskit app update-packages`, `jskit app link-local-packages`, and `jskit app release`, the app keeps the nice `npm run` shortcuts while the maintained behavior stays in the installed CLI package.
|
|
212
212
|
|
|
213
|
+
The Playwright scaffold follows the same rule. `playwright.config.mjs` delegates to `@jskit-ai/jskit-cli/test/playwright`, and the starter browser specs delegate their shared responsive checks to published JSKIT helpers. The generated files stay small while later JSKIT package updates can change local server startup, managed `PLAYWRIGHT_BASE_URL` handling, and `JSKIT_PLAYWRIGHT_STORAGE_STATE` support without copying that logic into each new app.
|
|
214
|
+
|
|
213
215
|
`jskit app verify` is worth noticing specifically. Linting, tests, and builds check your source code and runtime behavior. The JSKIT part of that flow runs `doctor`, which checks JSKIT-managed app state: installed package visibility, lock-file-backed managed files, and other JSKIT-specific health rules. It is there because a JSKIT app is not only code. It is also a descriptor-driven managed project.
|
|
214
216
|
|
|
215
217
|
The starter scaffold also writes `.github/workflows/jskit-verify.yml`. JSKIT generates and owns that workflow as a projection of installed package `ci` contracts. The baseline runs checkout, Node 26 setup, `npm ci`, and `npm run verify`. Packages can add job environment values, service containers, and explicit `before-verify` steps without taking ownership of the whole YAML file. For example, the database runtime adds migrations before verification. Its MySQL driver adds a MariaDB service with synthetic CI-only credentials and `DB_CLIENT=mysql2`; its Postgres driver adds the equivalent Postgres service and `DB_CLIENT=pg`.
|
|
@@ -818,6 +818,8 @@ That command does two things:
|
|
|
818
818
|
- runs the targeted Playwright command you give it
|
|
819
819
|
- writes a receipt describing the verified feature, auth mode, and current dirty UI file set
|
|
820
820
|
|
|
821
|
+
The auth mode is receipt metadata, not an authentication implementation. `jskit app verify-ui` does not log a user in, inject a secret, or rewrite the Playwright context. A direct local run uses `loginAsExistingUser()` from `@jskit-ai/auth-web/test/playwright`; a managed runner supplies authenticated state through `JSKIT_PLAYWRIGHT_STORAGE_STATE`. The generated `playwright.config.mjs` consumes that state and honors `PLAYWRIGHT_BASE_URL` without starting a second server.
|
|
822
|
+
|
|
821
823
|
`doctor` then compares the current changed UI files to that receipt. If you edit the UI again afterwards, the receipt is stale and `doctor` tells you to rerun `jskit app verify-ui`.
|
|
822
824
|
|
|
823
825
|
For local pre-merge review, use `--against <base-ref>` so JSKIT compares against the branch delta instead of only the current dirty worktree. The common shape is:
|
package/package.json
CHANGED
package/patterns/ui-testing.md
CHANGED
|
@@ -11,80 +11,88 @@ Rules:
|
|
|
11
11
|
|
|
12
12
|
- Any chunk that adds or changes user-facing UI must include a Playwright flow that exercises the changed behavior before the chunk is done.
|
|
13
13
|
- Generator or package template UI changes must be checked at compact phone, tablet-ish medium, and expanded desktop widths.
|
|
14
|
-
- For generated UI,
|
|
15
|
-
- Apps with `shell-web` installed should start from
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
- Do not
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- Make the bootstrap request from the same browser context that will run the assertions so the auth cookies land in the page session.
|
|
28
|
-
- Use stable seeded users or fixtures for Playwright. Do not depend on whatever account happens to exist in a developer's browser or external auth provider.
|
|
29
|
-
|
|
30
|
-
Recommended flow:
|
|
14
|
+
- For generated UI, check horizontal overflow, clipped or invisible text, duplicate navigation, broken route placement, and tap targets under 48 px.
|
|
15
|
+
- Apps with `shell-web` installed should start from `tests/e2e/adaptive-shell.spec.ts` and extend it with feature-specific assertions.
|
|
16
|
+
- Generated `playwright.config.mjs` delegates to `@jskit-ai/jskit-cli/test/playwright`. Do not copy base-URL, web-server, or storage-state logic into app tests.
|
|
17
|
+
- Use relative paths such as `page.goto("/home")`. The shared config owns the browser base URL.
|
|
18
|
+
- A managed runner supplies `PLAYWRIGHT_BASE_URL`. When it is set, JSKIT does not start another app server.
|
|
19
|
+
- A managed runner supplies an authenticated context through `JSKIT_PLAYWRIGHT_STORAGE_STATE`. Treat that file as a temporary secret: do not commit it, print it, or retain it after the run.
|
|
20
|
+
- Do not install a browser when the environment provides a managed browser runner.
|
|
21
|
+
|
|
22
|
+
## Direct local authentication
|
|
23
|
+
|
|
24
|
+
Use the development-only dev auth bypass when Playwright is talking directly to an app running on localhost.
|
|
25
|
+
|
|
26
|
+
The app must start with:
|
|
31
27
|
|
|
32
28
|
```bash
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
AUTH_DEV_BYPASS_ENABLED=true
|
|
30
|
+
AUTH_DEV_BYPASS_SECRET=replace-this-with-a-local-dev-secret
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The test uses the published Node-side helper:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { expect, test } from "@playwright/test";
|
|
37
|
+
import { loginAsExistingUser } from "@jskit-ai/auth-web/test/playwright";
|
|
38
|
+
|
|
39
|
+
test("authenticated feature", async ({ page }) => {
|
|
40
|
+
await loginAsExistingUser(page, { email: "ada@example.com" });
|
|
41
|
+
await page.goto("/w/acme/admin/contacts");
|
|
42
|
+
await expect(page.getByRole("heading", { name: "Contacts" })).toBeVisible();
|
|
43
|
+
});
|
|
37
44
|
```
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
`loginAsExistingUser()`:
|
|
47
|
+
|
|
48
|
+
- uses the request client belonging to the same Playwright browser context
|
|
49
|
+
- fetches the CSRF token from `GET /api/session`
|
|
50
|
+
- sends the CSRF token and private `x-jskit-dev-auth-secret` header to `POST /api/dev-auth/login-as`
|
|
51
|
+
- leaves the resulting HTTP-only cookies in that browser context
|
|
52
|
+
- refuses to send the secret to a non-local URL
|
|
53
|
+
|
|
54
|
+
The helper reads `AUTH_DEV_BYPASS_SECRET` from the Playwright Node process by default. Never pass that secret through `page.evaluate()`, browser globals, query parameters, or client-visible environment variables.
|
|
55
|
+
|
|
56
|
+
The route only selects an existing user. Seed a stable user or fixture before the test. The bypass must never be enabled in production.
|
|
57
|
+
|
|
58
|
+
## Managed-host authentication
|
|
59
|
+
|
|
60
|
+
A managed preview must not expose `AUTH_DEV_BYPASS_SECRET` to project code or forward an ordinary browser request to the private login exchange. The host performs its trusted identity exchange outside the application browser context, writes a temporary Playwright storage-state file, and launches the test with:
|
|
40
61
|
|
|
41
62
|
```bash
|
|
42
|
-
|
|
63
|
+
PLAYWRIGHT_BASE_URL=https://managed-preview.example.test \
|
|
64
|
+
JSKIT_PLAYWRIGHT_STORAGE_STATE=/secure/temp/playwright-state.json \
|
|
65
|
+
playwright test tests/e2e/contacts.spec.ts
|
|
43
66
|
```
|
|
44
67
|
|
|
45
|
-
The Playwright
|
|
68
|
+
The generated config applies that state to Playwright contexts and omits its local `webServer`. Tests then navigate with relative paths and begin with the runner-provided identity already authenticated.
|
|
46
69
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const sessionPayload = await sessionResponse.json();
|
|
59
|
-
const csrfToken = String(sessionPayload?.csrfToken || "");
|
|
60
|
-
if (!csrfToken) {
|
|
61
|
-
throw new Error("Missing csrfToken from /api/session.");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const loginResponse = await fetch("/api/dev-auth/login-as", {
|
|
65
|
-
method: "POST",
|
|
66
|
-
credentials: "include",
|
|
67
|
-
headers: {
|
|
68
|
-
"content-type": "application/json",
|
|
69
|
-
"csrf-token": csrfToken
|
|
70
|
-
},
|
|
71
|
-
body: JSON.stringify({ email })
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
if (!loginResponse.ok) {
|
|
75
|
-
throw new Error(`Dev login failed: ${loginResponse.status} ${await loginResponse.text()}`);
|
|
76
|
-
}
|
|
77
|
-
}, { email: "ada@example.com" });
|
|
70
|
+
Do not call `loginAsExistingUser()` against a managed preview. It is deliberately localhost-only. An ordinary request to `/api/dev-auth/login-as` without the private exchange header must return `403`.
|
|
71
|
+
|
|
72
|
+
## Recording verification
|
|
73
|
+
|
|
74
|
+
After the Playwright command succeeds, record it with:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx jskit app verify-ui \
|
|
78
|
+
--command "npx playwright test tests/e2e/contacts.spec.ts -g filters" \
|
|
79
|
+
--feature "contacts filters" \
|
|
80
|
+
--auth-mode dev-auth-login-as
|
|
78
81
|
```
|
|
79
82
|
|
|
80
|
-
|
|
83
|
+
Use `--auth-mode session-bootstrap` when a managed runner supplied authenticated storage state.
|
|
84
|
+
|
|
85
|
+
`jskit app verify-ui` runs the command and records its command, auth-mode label, feature, and changed UI files in `.jskit/verification/ui.json`. The auth-mode option describes how the command was authenticated; it does not create a session or modify the Playwright context.
|
|
86
|
+
|
|
87
|
+
For local pre-merge review, follow the recorded run with:
|
|
81
88
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
```bash
|
|
90
|
+
npx jskit doctor --against origin/main
|
|
91
|
+
```
|
|
85
92
|
|
|
86
93
|
Do not mark the chunk done if:
|
|
87
94
|
|
|
88
95
|
- the feature changed user-facing UI but no Playwright flow ran
|
|
89
96
|
- the Playwright flow skipped the changed behavior itself
|
|
90
|
-
-
|
|
97
|
+
- a managed environment was bypassed by installing another browser
|
|
98
|
+
- an authenticated flow was left untested without clearly reporting the missing session-bootstrap seam
|
|
@@ -116,6 +116,10 @@ Exports
|
|
|
116
116
|
Exports
|
|
117
117
|
- None
|
|
118
118
|
|
|
119
|
+
### `templates/base-shell/playwright.config.mjs`
|
|
120
|
+
Exports
|
|
121
|
+
- None
|
|
122
|
+
|
|
119
123
|
### `templates/base-shell/server.js`
|
|
120
124
|
Exports
|
|
121
125
|
- `createServer()`
|
|
@@ -266,6 +270,10 @@ Exports
|
|
|
266
270
|
Exports
|
|
267
271
|
- None
|
|
268
272
|
|
|
273
|
+
### `templates/minimal-shell/playwright.config.mjs`
|
|
274
|
+
Exports
|
|
275
|
+
- None
|
|
276
|
+
|
|
269
277
|
### `templates/minimal-shell/server.js`
|
|
270
278
|
Exports
|
|
271
279
|
- `createServer()`
|