@terpjs/conformance 0.11.0 → 0.13.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/dist/index.d.ts +47 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +111 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Page } from "@playwright/test";
|
|
1
|
+
import { type APIResponse, type Page } from "@playwright/test";
|
|
2
2
|
/**
|
|
3
3
|
* The administrator the base-profile flows sign in as. The default matches the bundled example
|
|
4
4
|
* workbench's seed; a generated repo points these at its own seeded admin via
|
|
@@ -23,6 +23,52 @@ export declare const VIEWER: {
|
|
|
23
23
|
email: string;
|
|
24
24
|
password: string;
|
|
25
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* One 429 the app answered while a spec was running: the throttle, as the server already
|
|
28
|
+
* described it.
|
|
29
|
+
*/
|
|
30
|
+
export interface ThrottleRecord {
|
|
31
|
+
/** The request that was refused. */
|
|
32
|
+
url: string;
|
|
33
|
+
/** Seconds until the window resets (`Retry-After`), or null when the header is absent. */
|
|
34
|
+
retryAfter: number | null;
|
|
35
|
+
/** The cap that was hit (`X-RateLimit-Limit`), or null. */
|
|
36
|
+
limit: number | null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Record the first 429 *page* receives, so a later failure can say what actually happened.
|
|
40
|
+
*
|
|
41
|
+
* The rate limiter is one fixed window keyed by client IP and applied to every request
|
|
42
|
+
* (240/60s by default, health checks included), so a conformance run that logs in, navigates
|
|
43
|
+
* and asserts can reach the cap on a shared runner — and every symptom of that is an element
|
|
44
|
+
* that never appears. The server already says so precisely: a typed `rate_limited` envelope
|
|
45
|
+
* with `Retry-After` and the `X-RateLimit-*` triple. Nothing in this harness read it, so the
|
|
46
|
+
* failure surfaced as a timeout on an unrelated locator, which is a debugging session about
|
|
47
|
+
* the wrong thing.
|
|
48
|
+
*
|
|
49
|
+
* FIRST rather than last: the first refusal is the one that broke the flow, and the ones
|
|
50
|
+
* after it are its consequences.
|
|
51
|
+
*/
|
|
52
|
+
export declare function watchForThrottling(page: Page): void;
|
|
53
|
+
/** The throttle *page* saw, if it saw one. */
|
|
54
|
+
export declare function throttleSeen(page: Page): ThrottleRecord | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Re-throw *error* as a throttle when the page was rate-limited, otherwise unchanged.
|
|
57
|
+
*
|
|
58
|
+
* Wrapping rather than replacing: a 429 seen during a run does not prove it caused this
|
|
59
|
+
* particular failure, so the original error stays in the message. What changes is that the
|
|
60
|
+
* reader is told a throttle happened at all, which is the fact that was on the wire and
|
|
61
|
+
* unread.
|
|
62
|
+
*/
|
|
63
|
+
export declare function explainThrottling(page: Page, error: unknown): unknown;
|
|
64
|
+
/**
|
|
65
|
+
* Fail with the throttle named when *response* is a 429, otherwise return it unchanged.
|
|
66
|
+
*
|
|
67
|
+
* The page-level watcher cannot see this one: an `APIRequestContext` emits no response
|
|
68
|
+
* events, so a suite that drives the API directly would still report `expect(response.ok())`
|
|
69
|
+
* as a bare false. The server's own envelope says exactly what happened; this reads it.
|
|
70
|
+
*/
|
|
71
|
+
export declare function assertNotThrottled(response: APIResponse): APIResponse;
|
|
26
72
|
/**
|
|
27
73
|
* Sign in through the real login screen. App-agnostic: the login screen and session are
|
|
28
74
|
* base-profile (identical in every Terp app), so this is the reusable entry point any app's
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,WAAW,EAAE,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAEvE;;;;;GAKG;AACH,eAAO,MAAM,KAAK;;;CAGjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,MAAM;;;CAGlB,CAAC;AAEF,eAAO,MAAM,MAAM;;;CAGlB,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,0FAA0F;IAC1F,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,2DAA2D;IAC3D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAkBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAYnD;AAED,8CAA8C;AAC9C,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,cAAc,GAAG,SAAS,CAEnE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAarE;AAWD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,WAAW,GAAG,WAAW,CAarE;AAED;;;;;GAKG;AACH,wBAAsB,KAAK,CACzB,IAAI,EAAE,IAAI,EACV,WAAW,GAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAU,GACvD,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAOtD"}
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,104 @@ export const VIEWER = {
|
|
|
23
23
|
email: process.env.TERP_E2E_VIEWER_EMAIL ?? "viewer@acme.test",
|
|
24
24
|
password: process.env.TERP_E2E_VIEWER_PASSWORD ?? "correct horse battery staple",
|
|
25
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* Every page being watched, and the first throttle each one saw.
|
|
28
|
+
*
|
|
29
|
+
* A WeakMap rather than a field on the page: the harness has no fixture of its own to hang
|
|
30
|
+
* state on, and a suite that opens several pages must not have one page's verdict explain
|
|
31
|
+
* another's.
|
|
32
|
+
*/
|
|
33
|
+
const THROTTLES = new WeakMap();
|
|
34
|
+
const WATCHED = new WeakSet();
|
|
35
|
+
function header(value) {
|
|
36
|
+
if (value === undefined)
|
|
37
|
+
return null;
|
|
38
|
+
const parsed = Number(value);
|
|
39
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Record the first 429 *page* receives, so a later failure can say what actually happened.
|
|
43
|
+
*
|
|
44
|
+
* The rate limiter is one fixed window keyed by client IP and applied to every request
|
|
45
|
+
* (240/60s by default, health checks included), so a conformance run that logs in, navigates
|
|
46
|
+
* and asserts can reach the cap on a shared runner — and every symptom of that is an element
|
|
47
|
+
* that never appears. The server already says so precisely: a typed `rate_limited` envelope
|
|
48
|
+
* with `Retry-After` and the `X-RateLimit-*` triple. Nothing in this harness read it, so the
|
|
49
|
+
* failure surfaced as a timeout on an unrelated locator, which is a debugging session about
|
|
50
|
+
* the wrong thing.
|
|
51
|
+
*
|
|
52
|
+
* FIRST rather than last: the first refusal is the one that broke the flow, and the ones
|
|
53
|
+
* after it are its consequences.
|
|
54
|
+
*/
|
|
55
|
+
export function watchForThrottling(page) {
|
|
56
|
+
if (WATCHED.has(page))
|
|
57
|
+
return;
|
|
58
|
+
WATCHED.add(page);
|
|
59
|
+
page.on("response", (response) => {
|
|
60
|
+
if (response.status() !== 429 || THROTTLES.has(page))
|
|
61
|
+
return;
|
|
62
|
+
const headers = response.headers();
|
|
63
|
+
THROTTLES.set(page, {
|
|
64
|
+
url: response.url(),
|
|
65
|
+
retryAfter: header(headers["retry-after"]),
|
|
66
|
+
limit: header(headers["x-ratelimit-limit"]),
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/** The throttle *page* saw, if it saw one. */
|
|
71
|
+
export function throttleSeen(page) {
|
|
72
|
+
return THROTTLES.get(page);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Re-throw *error* as a throttle when the page was rate-limited, otherwise unchanged.
|
|
76
|
+
*
|
|
77
|
+
* Wrapping rather than replacing: a 429 seen during a run does not prove it caused this
|
|
78
|
+
* particular failure, so the original error stays in the message. What changes is that the
|
|
79
|
+
* reader is told a throttle happened at all, which is the fact that was on the wire and
|
|
80
|
+
* unread.
|
|
81
|
+
*/
|
|
82
|
+
export function explainThrottling(page, error) {
|
|
83
|
+
const throttle = THROTTLES.get(page);
|
|
84
|
+
if (throttle === undefined)
|
|
85
|
+
return error;
|
|
86
|
+
const wait = throttle.retryAfter === null ? "" : `, Retry-After ${throttle.retryAfter}s`;
|
|
87
|
+
const cap = throttle.limit === null ? "" : ` (cap ${throttle.limit} requests/window)`;
|
|
88
|
+
return new Error(`the run was rate-limited (429${wait})${cap}: ${throttle.url}\n` +
|
|
89
|
+
" One fixed window keyed by client IP covers every request, so a whole suite " +
|
|
90
|
+
"sharing a runner can exhaust it and every symptom looks like a missing element.\n" +
|
|
91
|
+
" Raise the cap for the workbench via SecurityConfig(rate_limit=RateLimit(...)), " +
|
|
92
|
+
"or serialise the suite.\n" +
|
|
93
|
+
` The assertion that failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
94
|
+
}
|
|
95
|
+
/** Run *body*, and explain a failure as a throttle when the page saw one. */
|
|
96
|
+
async function orThrottle(page, body) {
|
|
97
|
+
try {
|
|
98
|
+
return await body();
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
throw explainThrottling(page, error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Fail with the throttle named when *response* is a 429, otherwise return it unchanged.
|
|
106
|
+
*
|
|
107
|
+
* The page-level watcher cannot see this one: an `APIRequestContext` emits no response
|
|
108
|
+
* events, so a suite that drives the API directly would still report `expect(response.ok())`
|
|
109
|
+
* as a bare false. The server's own envelope says exactly what happened; this reads it.
|
|
110
|
+
*/
|
|
111
|
+
export function assertNotThrottled(response) {
|
|
112
|
+
if (response.status() !== 429)
|
|
113
|
+
return response;
|
|
114
|
+
const headers = response.headers();
|
|
115
|
+
const retryAfter = headers["retry-after"];
|
|
116
|
+
const limit = headers["x-ratelimit-limit"];
|
|
117
|
+
throw new Error(`the run was rate-limited (429${retryAfter === undefined ? "" : `, Retry-After ${retryAfter}s`})` +
|
|
118
|
+
`${limit === undefined ? "" : ` (cap ${limit} requests/window)`}: ${response.url()}\n` +
|
|
119
|
+
" One fixed window keyed by client IP covers every request, so a whole suite " +
|
|
120
|
+
"sharing a runner can exhaust it.\n" +
|
|
121
|
+
" Raise the cap for the workbench via SecurityConfig(rate_limit=RateLimit(...)), " +
|
|
122
|
+
"or serialise the suite.");
|
|
123
|
+
}
|
|
26
124
|
/**
|
|
27
125
|
* Sign in through the real login screen. App-agnostic: the login screen and session are
|
|
28
126
|
* base-profile (identical in every Terp app), so this is the reusable entry point any app's
|
|
@@ -30,8 +128,11 @@ export const VIEWER = {
|
|
|
30
128
|
* callers assert their own landing content afterwards.
|
|
31
129
|
*/
|
|
32
130
|
export async function login(page, credentials = ADMIN) {
|
|
33
|
-
|
|
34
|
-
await
|
|
131
|
+
watchForThrottling(page);
|
|
132
|
+
await orThrottle(page, async () => {
|
|
133
|
+
await page.goto("/");
|
|
134
|
+
await expect(page.getByRole("heading", { name: "Sign in" })).toBeVisible();
|
|
135
|
+
});
|
|
35
136
|
// By LABEL, not by placeholder. The login fields were placeholder-only until the labels
|
|
36
137
|
// landed, and a placeholder was the only handle they had — which is the same defect from
|
|
37
138
|
// the other side: a name that vanishes the moment a user types is not a name. Selecting
|
|
@@ -41,9 +142,9 @@ export async function login(page, credentials = ADMIN) {
|
|
|
41
142
|
await page.getByLabel("Email", { exact: true }).fill(credentials.email);
|
|
42
143
|
await page.getByLabel("Password", { exact: true }).fill(credentials.password);
|
|
43
144
|
await page.getByRole("button", { name: "Sign in" }).click();
|
|
44
|
-
await expect(page.getByRole("heading", { name: "Sign in" })).toHaveCount(0, {
|
|
145
|
+
await orThrottle(page, () => expect(page.getByRole("heading", { name: "Sign in" })).toHaveCount(0, {
|
|
45
146
|
timeout: 15_000,
|
|
46
|
-
});
|
|
147
|
+
}));
|
|
47
148
|
}
|
|
48
149
|
/**
|
|
49
150
|
* Sign out through the shell's account menu and assert the session is gone. Base-profile: the
|
|
@@ -62,8 +163,11 @@ export async function login(page, credentials = ADMIN) {
|
|
|
62
163
|
* being a pinned inventory whose renames are release notes.
|
|
63
164
|
*/
|
|
64
165
|
export async function logout(page) {
|
|
65
|
-
|
|
66
|
-
await page
|
|
67
|
-
|
|
166
|
+
watchForThrottling(page);
|
|
167
|
+
await orThrottle(page, async () => {
|
|
168
|
+
await page.locator('[data-terp="user-menu"] [data-terp="menu-trigger"]').click();
|
|
169
|
+
await page.getByRole("menuitem", { name: "Sign out" }).click();
|
|
170
|
+
await expect(page.getByRole("heading", { name: "Sign in" })).toBeVisible();
|
|
171
|
+
});
|
|
68
172
|
}
|
|
69
173
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@terpjs/conformance",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Terp conformance \u2014 app-agnostic Playwright end-to-end flows over a running Terp stack (base profile: auth gating + session lifecycle) plus the login/role helpers an app's own e2e suite composes.",
|
|
6
6
|
"main": "./dist/index.js",
|