@rstest/browser 0.8.5 → 0.9.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/LICENSE-APACHE-2.0 +202 -0
- package/NOTICE +11 -0
- package/dist/361.js +8 -0
- package/dist/augmentExpect.d.ts +73 -0
- package/dist/browser-container/container-static/css/index.5c72297783.css +1 -0
- package/dist/browser-container/container-static/js/{565.226c9ef5.js → 101.36a8ccdf84.js} +4024 -3856
- package/dist/browser-container/container-static/js/101.36a8ccdf84.js.LICENSE.txt +1 -0
- package/dist/browser-container/container-static/js/{index.c1d17467.js → index.0687a8142a.js} +742 -692
- package/dist/browser-container/container-static/js/{lib-react.97ee79b0.js → lib-react.dcf2a5e57a.js} +10 -10
- package/dist/browser-container/container-static/js/lib-react.dcf2a5e57a.js.LICENSE.txt +1 -0
- package/dist/browser-container/index.html +1 -1
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +583 -0
- package/dist/browserRpcRegistry.d.ts +18 -0
- package/dist/client/api.d.ts +3 -0
- package/dist/client/browserRpc.d.ts +2 -0
- package/dist/client/dispatchTransport.d.ts +11 -0
- package/dist/client/entry.d.ts +1 -5
- package/dist/client/locator.d.ts +125 -0
- package/dist/client/snapshot.d.ts +0 -6
- package/dist/concurrency.d.ts +12 -0
- package/dist/dispatchCapabilities.d.ts +34 -0
- package/dist/dispatchRouter.d.ts +20 -0
- package/dist/headlessLatestRerunScheduler.d.ts +19 -0
- package/dist/headlessTransport.d.ts +12 -0
- package/dist/index.js +1580 -258
- package/dist/protocol.d.ts +44 -33
- package/dist/providers/index.d.ts +79 -0
- package/dist/providers/playwright/compileLocator.d.ts +3 -0
- package/dist/providers/playwright/dispatchBrowserRpc.d.ts +13 -0
- package/dist/providers/playwright/expectUtils.d.ts +24 -0
- package/dist/providers/playwright/implementation.d.ts +2 -0
- package/dist/providers/playwright/index.d.ts +1 -0
- package/dist/providers/playwright/runtime.d.ts +5 -0
- package/dist/providers/playwright/textMatcher.d.ts +8 -0
- package/dist/rpcProtocol.d.ts +145 -0
- package/dist/runSession.d.ts +33 -0
- package/dist/sessionRegistry.d.ts +34 -0
- package/dist/sourceMap/sourceMapLoader.d.ts +14 -0
- package/dist/watchRerunPlanner.d.ts +21 -0
- package/package.json +15 -10
- package/src/AGENTS.md +128 -0
- package/src/augmentExpect.ts +62 -0
- package/src/browser.ts +3 -0
- package/src/browserRpcRegistry.ts +57 -0
- package/src/client/AGENTS.md +82 -0
- package/src/client/api.ts +213 -0
- package/src/client/browserRpc.ts +86 -0
- package/src/client/dispatchTransport.ts +178 -0
- package/src/client/entry.ts +96 -33
- package/src/client/locator.ts +452 -0
- package/src/client/snapshot.ts +32 -97
- package/src/client/sourceMapSupport.ts +26 -37
- package/src/concurrency.ts +62 -0
- package/src/dispatchCapabilities.ts +162 -0
- package/src/dispatchRouter.ts +82 -0
- package/src/env.d.ts +8 -1
- package/src/headlessLatestRerunScheduler.ts +76 -0
- package/src/headlessTransport.ts +28 -0
- package/src/hostController.ts +1292 -367
- package/src/protocol.ts +66 -31
- package/src/providers/index.ts +103 -0
- package/src/providers/playwright/compileLocator.ts +130 -0
- package/src/providers/playwright/dispatchBrowserRpc.ts +372 -0
- package/src/providers/playwright/expectUtils.ts +57 -0
- package/src/providers/playwright/implementation.ts +33 -0
- package/src/providers/playwright/index.ts +1 -0
- package/src/providers/playwright/runtime.ts +32 -0
- package/src/providers/playwright/textMatcher.ts +10 -0
- package/src/rpcProtocol.ts +220 -0
- package/src/runSession.ts +110 -0
- package/src/sessionRegistry.ts +89 -0
- package/src/sourceMap/sourceMapLoader.ts +96 -0
- package/src/watchRerunPlanner.ts +77 -0
- package/dist/browser-container/container-static/css/index.5a71c757.css +0 -1
- package/dist/browser-container/container-static/js/565.226c9ef5.js.LICENSE.txt +0 -1
- package/dist/browser-container/container-static/js/lib-react.97ee79b0.js.LICENSE.txt +0 -1
- package/dist/browser-container/container-static/js/scheduler.5accca0c.js +0 -407
- package/dist/browser-container/scheduler.html +0 -19
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
export type BrowserLocatorText =
|
|
2
|
+
| { type: 'string'; value: string }
|
|
3
|
+
| { type: 'regexp'; source: string; flags?: string };
|
|
4
|
+
|
|
5
|
+
export type BrowserLocatorStep =
|
|
6
|
+
| {
|
|
7
|
+
type: 'getByRole';
|
|
8
|
+
role: string;
|
|
9
|
+
options?: {
|
|
10
|
+
name?: BrowserLocatorText;
|
|
11
|
+
exact?: boolean;
|
|
12
|
+
checked?: boolean;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
expanded?: boolean;
|
|
15
|
+
selected?: boolean;
|
|
16
|
+
pressed?: boolean;
|
|
17
|
+
includeHidden?: boolean;
|
|
18
|
+
level?: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
| {
|
|
22
|
+
type: 'locator';
|
|
23
|
+
selector: string;
|
|
24
|
+
}
|
|
25
|
+
| {
|
|
26
|
+
type: 'getByText';
|
|
27
|
+
text: BrowserLocatorText;
|
|
28
|
+
options?: { exact?: boolean };
|
|
29
|
+
}
|
|
30
|
+
| {
|
|
31
|
+
type: 'getByLabel';
|
|
32
|
+
text: BrowserLocatorText;
|
|
33
|
+
options?: { exact?: boolean };
|
|
34
|
+
}
|
|
35
|
+
| {
|
|
36
|
+
type: 'getByPlaceholder';
|
|
37
|
+
text: BrowserLocatorText;
|
|
38
|
+
options?: { exact?: boolean };
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
type: 'getByAltText';
|
|
42
|
+
text: BrowserLocatorText;
|
|
43
|
+
options?: { exact?: boolean };
|
|
44
|
+
}
|
|
45
|
+
| {
|
|
46
|
+
type: 'getByTitle';
|
|
47
|
+
text: BrowserLocatorText;
|
|
48
|
+
options?: { exact?: boolean };
|
|
49
|
+
}
|
|
50
|
+
| {
|
|
51
|
+
type: 'getByTestId';
|
|
52
|
+
text: BrowserLocatorText;
|
|
53
|
+
}
|
|
54
|
+
| {
|
|
55
|
+
type: 'filter';
|
|
56
|
+
options: {
|
|
57
|
+
hasText?: BrowserLocatorText;
|
|
58
|
+
hasNotText?: BrowserLocatorText;
|
|
59
|
+
has?: BrowserLocatorIR;
|
|
60
|
+
hasNot?: BrowserLocatorIR;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
| { type: 'and'; locator: BrowserLocatorIR }
|
|
64
|
+
| { type: 'or'; locator: BrowserLocatorIR }
|
|
65
|
+
| { type: 'nth'; index: number }
|
|
66
|
+
| { type: 'first' }
|
|
67
|
+
| { type: 'last' };
|
|
68
|
+
|
|
69
|
+
export type BrowserLocatorIR = {
|
|
70
|
+
steps: BrowserLocatorStep[];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type BrowserRpcRequest = {
|
|
74
|
+
id: string;
|
|
75
|
+
/** Absolute test file path for locating runner iframe */
|
|
76
|
+
testPath: string;
|
|
77
|
+
/** Run identifier generated by container for stale-request protection. */
|
|
78
|
+
runId: string;
|
|
79
|
+
kind: 'locator' | 'expect' | 'config';
|
|
80
|
+
locator: BrowserLocatorIR;
|
|
81
|
+
method: string;
|
|
82
|
+
args: unknown[];
|
|
83
|
+
/**
|
|
84
|
+
* Negation for expect matchers (equivalent to Playwright expect(...).not).
|
|
85
|
+
* Only meaningful for kind === 'expect'.
|
|
86
|
+
*/
|
|
87
|
+
isNot?: boolean;
|
|
88
|
+
/** Optional timeout override (ms). Falls back to browser rpcTimeout. */
|
|
89
|
+
timeout?: number;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => {
|
|
93
|
+
return typeof value === 'object' && value !== null;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const readString = (
|
|
97
|
+
value: Record<string, unknown>,
|
|
98
|
+
key: string,
|
|
99
|
+
label: string,
|
|
100
|
+
): string => {
|
|
101
|
+
const result = value[key];
|
|
102
|
+
if (typeof result !== 'string') {
|
|
103
|
+
throw new Error(`Invalid browser RPC request: ${label} must be a string`);
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const readUnknownArray = (
|
|
109
|
+
value: Record<string, unknown>,
|
|
110
|
+
key: string,
|
|
111
|
+
label: string,
|
|
112
|
+
): unknown[] => {
|
|
113
|
+
const result = value[key];
|
|
114
|
+
if (!Array.isArray(result)) {
|
|
115
|
+
throw new Error(`Invalid browser RPC request: ${label} must be an array`);
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const parseBrowserLocatorIR = (
|
|
121
|
+
value: unknown,
|
|
122
|
+
label: string,
|
|
123
|
+
): BrowserLocatorIR => {
|
|
124
|
+
if (!isRecord(value)) {
|
|
125
|
+
throw new Error(`Invalid browser RPC request: ${label} must be an object`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const steps = value.steps;
|
|
129
|
+
if (!Array.isArray(steps)) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`Invalid browser RPC request: ${label}.steps must be an array`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return value as BrowserLocatorIR;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const validateBrowserRpcRequest = (
|
|
139
|
+
payload: unknown,
|
|
140
|
+
): BrowserRpcRequest => {
|
|
141
|
+
if (!isRecord(payload)) {
|
|
142
|
+
throw new Error('Invalid browser RPC request: payload must be an object');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const kind = readString(payload, 'kind', 'kind');
|
|
146
|
+
if (kind !== 'locator' && kind !== 'expect' && kind !== 'config') {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`Invalid browser RPC request: unsupported kind ${JSON.stringify(kind)}`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const request: BrowserRpcRequest = {
|
|
153
|
+
id: readString(payload, 'id', 'id'),
|
|
154
|
+
testPath: readString(payload, 'testPath', 'testPath'),
|
|
155
|
+
runId: readString(payload, 'runId', 'runId'),
|
|
156
|
+
kind,
|
|
157
|
+
locator: parseBrowserLocatorIR(payload.locator, 'locator'),
|
|
158
|
+
method: readString(payload, 'method', 'method'),
|
|
159
|
+
args: readUnknownArray(payload, 'args', 'args'),
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const isNot = payload.isNot;
|
|
163
|
+
if (isNot !== undefined) {
|
|
164
|
+
if (typeof isNot !== 'boolean') {
|
|
165
|
+
throw new Error('Invalid browser RPC request: isNot must be a boolean');
|
|
166
|
+
}
|
|
167
|
+
request.isNot = isNot;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const timeout = payload.timeout;
|
|
171
|
+
if (timeout !== undefined) {
|
|
172
|
+
if (typeof timeout !== 'number') {
|
|
173
|
+
throw new Error('Invalid browser RPC request: timeout must be a number');
|
|
174
|
+
}
|
|
175
|
+
request.timeout = timeout;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return request;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export type BrowserRpcResponse = {
|
|
182
|
+
id: string;
|
|
183
|
+
result?: unknown;
|
|
184
|
+
error?: string;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Snapshot RPC request from runner iframe.
|
|
189
|
+
* The container will forward these to the host via WebSocket RPC.
|
|
190
|
+
*/
|
|
191
|
+
export type SnapshotRpcRequest =
|
|
192
|
+
| {
|
|
193
|
+
id: string;
|
|
194
|
+
method: 'resolveSnapshotPath';
|
|
195
|
+
args: { testPath: string };
|
|
196
|
+
}
|
|
197
|
+
| {
|
|
198
|
+
id: string;
|
|
199
|
+
method: 'readSnapshotFile';
|
|
200
|
+
args: { filepath: string };
|
|
201
|
+
}
|
|
202
|
+
| {
|
|
203
|
+
id: string;
|
|
204
|
+
method: 'saveSnapshotFile';
|
|
205
|
+
args: { filepath: string; content: string };
|
|
206
|
+
}
|
|
207
|
+
| {
|
|
208
|
+
id: string;
|
|
209
|
+
method: 'removeSnapshotFile';
|
|
210
|
+
args: { filepath: string };
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Snapshot RPC response from container to runner iframe.
|
|
215
|
+
*/
|
|
216
|
+
export type SnapshotRpcResponse = {
|
|
217
|
+
id: string;
|
|
218
|
+
result?: unknown;
|
|
219
|
+
error?: string;
|
|
220
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run generation/session lifecycle contract shared by browser scheduling paths.
|
|
3
|
+
*/
|
|
4
|
+
export type RunSession = {
|
|
5
|
+
token: number;
|
|
6
|
+
cancelled: boolean;
|
|
7
|
+
cancelSignal: Promise<void>;
|
|
8
|
+
signalCancel: () => void;
|
|
9
|
+
done?: Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const createCancelSignal = (): {
|
|
13
|
+
signal: Promise<void>;
|
|
14
|
+
resolve: () => void;
|
|
15
|
+
} => {
|
|
16
|
+
let settled = false;
|
|
17
|
+
let resolveSignal: () => void = () => {};
|
|
18
|
+
|
|
19
|
+
const signal = new Promise<void>((resolve) => {
|
|
20
|
+
resolveSignal = () => {
|
|
21
|
+
if (!settled) {
|
|
22
|
+
settled = true;
|
|
23
|
+
resolve();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
signal,
|
|
30
|
+
resolve: resolveSignal,
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const createRunSession = (token: number): RunSession => {
|
|
35
|
+
const { signal, resolve } = createCancelSignal();
|
|
36
|
+
return {
|
|
37
|
+
token,
|
|
38
|
+
cancelled: false,
|
|
39
|
+
cancelSignal: signal,
|
|
40
|
+
signalCancel: resolve,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type CancelOptions<T extends RunSession> = {
|
|
45
|
+
waitForDone?: boolean;
|
|
46
|
+
onCancel?: (session: T) => Promise<void> | void;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Canonical run-token lifecycle manager.
|
|
51
|
+
* Centralizes token increment, invalidation, and cancellation semantics.
|
|
52
|
+
*/
|
|
53
|
+
export class RunSessionLifecycle<T extends RunSession> {
|
|
54
|
+
private currentToken = 0;
|
|
55
|
+
private active: T | null = null;
|
|
56
|
+
|
|
57
|
+
get activeSession(): T | null {
|
|
58
|
+
return this.active;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get activeToken(): number {
|
|
62
|
+
return this.currentToken;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
createSession(factory: (token: number) => T): T {
|
|
66
|
+
const session = factory(++this.currentToken);
|
|
67
|
+
this.active = session;
|
|
68
|
+
return session;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
isTokenActive(token: number): boolean {
|
|
72
|
+
return token === this.currentToken;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
isTokenStale(token: number): boolean {
|
|
76
|
+
return !this.isTokenActive(token);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
invalidateActiveToken(): number {
|
|
80
|
+
this.currentToken += 1;
|
|
81
|
+
return this.currentToken;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
clearIfActive(session: T): void {
|
|
85
|
+
if (this.active === session) {
|
|
86
|
+
this.active = null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async cancel(session: T, options?: CancelOptions<T>): Promise<void> {
|
|
91
|
+
const waitForDone = options?.waitForDone ?? true;
|
|
92
|
+
|
|
93
|
+
if (!session.cancelled) {
|
|
94
|
+
session.cancelled = true;
|
|
95
|
+
session.signalCancel();
|
|
96
|
+
await options?.onCancel?.(session);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (waitForDone) {
|
|
100
|
+
await session.done?.catch(() => {});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async cancelActive(options?: CancelOptions<T>): Promise<void> {
|
|
105
|
+
if (!this.active) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
await this.cancel(this.active, options);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { BrowserProviderContext, BrowserProviderPage } from './providers';
|
|
2
|
+
|
|
3
|
+
export type RunnerSessionRecord = {
|
|
4
|
+
id: string;
|
|
5
|
+
testFile: string;
|
|
6
|
+
projectName: string;
|
|
7
|
+
runToken: number;
|
|
8
|
+
mode: 'headless-page' | 'headed-iframe';
|
|
9
|
+
createdAt: number;
|
|
10
|
+
context?: BrowserProviderContext;
|
|
11
|
+
page?: BrowserProviderPage;
|
|
12
|
+
metadata?: Record<string, unknown>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type RunnerSessionInput = Omit<RunnerSessionRecord, 'id' | 'createdAt'> & {
|
|
16
|
+
id?: string;
|
|
17
|
+
createdAt?: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Execution target index for host scheduling.
|
|
22
|
+
* Provides lookup by session id, test file, and run token without coupling to UI topology.
|
|
23
|
+
*/
|
|
24
|
+
export class RunnerSessionRegistry {
|
|
25
|
+
private nextId = 0;
|
|
26
|
+
private sessionsById = new Map<string, RunnerSessionRecord>();
|
|
27
|
+
private sessionIdByTestFile = new Map<string, string>();
|
|
28
|
+
|
|
29
|
+
register(input: RunnerSessionInput): RunnerSessionRecord {
|
|
30
|
+
const id = input.id ?? `runner-session-${++this.nextId}`;
|
|
31
|
+
const createdAt = input.createdAt ?? Date.now();
|
|
32
|
+
|
|
33
|
+
const record: RunnerSessionRecord = {
|
|
34
|
+
...input,
|
|
35
|
+
id,
|
|
36
|
+
createdAt,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
this.sessionsById.set(id, record);
|
|
40
|
+
this.sessionIdByTestFile.set(record.testFile, id);
|
|
41
|
+
return record;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getById(id: string): RunnerSessionRecord | undefined {
|
|
45
|
+
return this.sessionsById.get(id);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getByTestFile(testFile: string): RunnerSessionRecord | undefined {
|
|
49
|
+
const id = this.sessionIdByTestFile.get(testFile);
|
|
50
|
+
if (!id) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return this.sessionsById.get(id);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
list(): RunnerSessionRecord[] {
|
|
57
|
+
return Array.from(this.sessionsById.values());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
listByRunToken(runToken: number): RunnerSessionRecord[] {
|
|
61
|
+
return this.list().filter((session) => session.runToken === runToken);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
deleteById(id: string): boolean {
|
|
65
|
+
const record = this.sessionsById.get(id);
|
|
66
|
+
if (!record) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.sessionsById.delete(id);
|
|
71
|
+
if (this.sessionIdByTestFile.get(record.testFile) === id) {
|
|
72
|
+
this.sessionIdByTestFile.delete(record.testFile);
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
deleteByTestFile(testFile: string): boolean {
|
|
78
|
+
const id = this.sessionIdByTestFile.get(testFile);
|
|
79
|
+
if (!id) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return this.deleteById(id);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
clear(): void {
|
|
86
|
+
this.sessionsById.clear();
|
|
87
|
+
this.sessionIdByTestFile.clear();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DecodedSourceMapXInput,
|
|
3
|
+
EncodedSourceMapXInput,
|
|
4
|
+
} from '@jridgewell/trace-mapping';
|
|
5
|
+
import convert from 'convert-source-map';
|
|
6
|
+
|
|
7
|
+
export type SourceMapPayload = EncodedSourceMapXInput | DecodedSourceMapXInput;
|
|
8
|
+
|
|
9
|
+
type Fetcher = typeof fetch;
|
|
10
|
+
|
|
11
|
+
export const normalizeJavaScriptUrl = (
|
|
12
|
+
value: string,
|
|
13
|
+
options?: {
|
|
14
|
+
origin?: string;
|
|
15
|
+
},
|
|
16
|
+
): string | null => {
|
|
17
|
+
try {
|
|
18
|
+
const url = options?.origin
|
|
19
|
+
? new URL(value, options.origin)
|
|
20
|
+
: new URL(value);
|
|
21
|
+
|
|
22
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
url.search = '';
|
|
27
|
+
url.hash = '';
|
|
28
|
+
return url.toString();
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const resolveInlineSourceMap = (code: string): SourceMapPayload | null => {
|
|
35
|
+
const converter = convert.fromSource(code);
|
|
36
|
+
if (!converter) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return converter.toObject() as SourceMapPayload;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const fetchSourceMap = async (
|
|
44
|
+
jsUrl: string,
|
|
45
|
+
fetcher: Fetcher,
|
|
46
|
+
): Promise<SourceMapPayload | null> => {
|
|
47
|
+
const jsResponse = await fetcher(jsUrl);
|
|
48
|
+
if (!jsResponse.ok) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const code = await jsResponse.text();
|
|
53
|
+
const inlineMap = resolveInlineSourceMap(code);
|
|
54
|
+
if (inlineMap) {
|
|
55
|
+
return inlineMap;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const mapResponse = await fetcher(`${jsUrl}.map`);
|
|
59
|
+
if (!mapResponse.ok) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return (await mapResponse.json()) as SourceMapPayload;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const loadSourceMapWithCache = async ({
|
|
67
|
+
jsUrl,
|
|
68
|
+
cache,
|
|
69
|
+
force = false,
|
|
70
|
+
origin,
|
|
71
|
+
fetcher = fetch,
|
|
72
|
+
}: {
|
|
73
|
+
jsUrl: string;
|
|
74
|
+
cache: Map<string, SourceMapPayload | null>;
|
|
75
|
+
force?: boolean;
|
|
76
|
+
origin?: string;
|
|
77
|
+
fetcher?: Fetcher;
|
|
78
|
+
}): Promise<SourceMapPayload | null> => {
|
|
79
|
+
const normalizedUrl = normalizeJavaScriptUrl(jsUrl, { origin });
|
|
80
|
+
if (!normalizedUrl) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!force && cache.has(normalizedUrl)) {
|
|
85
|
+
return cache.get(normalizedUrl) ?? null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const sourceMap = await fetchSourceMap(normalizedUrl, fetcher);
|
|
90
|
+
cache.set(normalizedUrl, sourceMap);
|
|
91
|
+
return sourceMap;
|
|
92
|
+
} catch {
|
|
93
|
+
cache.set(normalizedUrl, null);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { normalize } from 'pathe';
|
|
2
|
+
import type { TestFileInfo } from './protocol';
|
|
3
|
+
|
|
4
|
+
export type WatchPlannerProjectEntry = {
|
|
5
|
+
project: {
|
|
6
|
+
name: string;
|
|
7
|
+
};
|
|
8
|
+
testFiles: string[];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type WatchRerunPlannerInput = {
|
|
12
|
+
projectEntries: WatchPlannerProjectEntry[];
|
|
13
|
+
previousTestFiles: TestFileInfo[];
|
|
14
|
+
affectedTestFiles: string[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type WatchRerunPlan = {
|
|
18
|
+
currentTestFiles: TestFileInfo[];
|
|
19
|
+
filesChanged: boolean;
|
|
20
|
+
normalizedAffectedTestFiles: string[];
|
|
21
|
+
affectedTestFiles: TestFileInfo[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const serializeTestFiles = (files: TestFileInfo[]): string => {
|
|
25
|
+
return JSON.stringify(
|
|
26
|
+
files.map((f) => `${f.projectName}:${f.testPath}`).sort(),
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const normalizeTestFiles = (files: TestFileInfo[]): TestFileInfo[] => {
|
|
31
|
+
return files.map((file) => ({
|
|
32
|
+
...file,
|
|
33
|
+
testPath: normalize(file.testPath),
|
|
34
|
+
}));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const collectWatchTestFiles = (
|
|
38
|
+
projectEntries: WatchPlannerProjectEntry[],
|
|
39
|
+
): TestFileInfo[] => {
|
|
40
|
+
return projectEntries.flatMap((entry) =>
|
|
41
|
+
entry.testFiles.map((testPath) => ({
|
|
42
|
+
testPath: normalize(testPath),
|
|
43
|
+
projectName: entry.project.name,
|
|
44
|
+
})),
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const planWatchRerun = ({
|
|
49
|
+
projectEntries,
|
|
50
|
+
previousTestFiles,
|
|
51
|
+
affectedTestFiles,
|
|
52
|
+
}: WatchRerunPlannerInput): WatchRerunPlan => {
|
|
53
|
+
const currentTestFiles = collectWatchTestFiles(projectEntries);
|
|
54
|
+
const normalizedPrevious = normalizeTestFiles(previousTestFiles);
|
|
55
|
+
const filesChanged =
|
|
56
|
+
serializeTestFiles(currentTestFiles) !==
|
|
57
|
+
serializeTestFiles(normalizedPrevious);
|
|
58
|
+
|
|
59
|
+
const normalizedAffectedTestFiles = affectedTestFiles.map((testFile) =>
|
|
60
|
+
normalize(testFile),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const currentFileMap = new Map(
|
|
64
|
+
currentTestFiles.map((file) => [file.testPath, file] as const),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const matchedAffectedFiles = normalizedAffectedTestFiles
|
|
68
|
+
.map((testFile) => currentFileMap.get(testFile))
|
|
69
|
+
.filter((file): file is TestFileInfo => Boolean(file));
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
currentTestFiles,
|
|
73
|
+
filesChanged,
|
|
74
|
+
normalizedAffectedTestFiles,
|
|
75
|
+
affectedTestFiles: matchedAffectedFiles,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@import "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap";@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-duration:initial;--tw-ease:initial}::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-zinc-950:#09090b;--color-black:#000;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--font-weight-light:300;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wider:.05em;--tracking-widest:.1em;--radius-md:.375rem;--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1)}@supports (color:color(display-p3 0 0 0)){:root,:host{--color-zinc-950:color(display-p3 .0353716 .0353595 .0435539)}}@supports (color:lab(0% 0 0)){:root,:host{--color-zinc-950:lab(2.51107% .242703 -.886115)}}}@layer base,components;@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.inset-x-0{inset-inline:calc(var(--spacing)*0)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.bottom-0{bottom:calc(var(--spacing)*0)}.left-0{left:calc(var(--spacing)*0)}.z-10{z-index:10}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing)*0)}.mt-3{margin-top:calc(var(--spacing)*3)}.ml-1{margin-left:calc(var(--spacing)*1)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.h-2{height:calc(var(--spacing)*2)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-7{height:calc(var(--spacing)*7)}.h-8{height:calc(var(--spacing)*8)}.h-\[1px\]{height:1px}.h-\[48px\]{height:48px}.h-full{height:100%}.h-screen{height:100vh}.min-h-0{min-height:calc(var(--spacing)*0)}.w-2{width:calc(var(--spacing)*2)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-7{width:calc(var(--spacing)*7)}.w-8{width:calc(var(--spacing)*8)}.w-\[16px\]{width:16px}.w-\[120px\]{width:120px}.w-\[280px\]{width:280px}.w-full{width:100%}.max-w-\[400px\]{max-width:400px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.resize{resize:both}.grid-cols-\[auto_minmax\(0\,1fr\)_auto\]{grid-template-columns:auto minmax(0,1fr) auto}.flex-col{flex-direction:column}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.bg-black\/\[0\.02\]{background-color:#00000005}@supports (color:color-mix(in lab, red, red)){.bg-black\/\[0\.02\]{background-color:color-mix(in oklab,var(--color-black)2%,transparent)}}.bg-transparent{background-color:#0000}.bg-zinc-950{background-color:var(--color-zinc-950)}.p-0{padding:calc(var(--spacing)*0)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-4{padding-inline:calc(var(--spacing)*4)}.py-0{padding-block:calc(var(--spacing)*0)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-3{padding-block:calc(var(--spacing)*3)}.font-mono{font-family:var(--font-mono)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.leading-none{--tw-leading:1;line-height:1}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.text-\(--accents-3\){color:var(--accents-3)}.text-\(--accents-4\){color:var(--accents-4)}.text-\(--accents-5\){color:var(--accents-5)}.text-\(--accents-6\){color:var(--accents-6)}.text-\(--accents-7\){color:var(--accents-7)}.text-\(--muted-foreground\){color:var(--muted-foreground)}.text-\(--warning\){color:var(--warning)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal, )var(--tw-slashed-zero, )var(--tw-numeric-figure, )var(--tw-numeric-spacing, )var(--tw-numeric-fraction, )}.opacity-0{opacity:0}.opacity-40{opacity:.4}.opacity-70{opacity:.7}.opacity-80{opacity:.8}.opacity-90{opacity:.9}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.ease-\[cubic-bezier\(0\.4\,0\,0\.2\,1\)\]{--tw-ease:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(.4,0,.2,1)}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.hover\:bg-\(--accents-1\):hover{background-color:var(--accents-1)}.hover\:text-\(--accents-6\):hover{color:var(--accents-6)}}}.ant-typography.font-mono{font-family:var(--font-mono)}:root{--font-mono:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--background:#fff;--foreground:#000;--ds-gray-100:#fafafa;--ds-gray-200:#eaeaea;--ds-gray-300:#999;--ds-gray-400:#888;--ds-gray-500:#666;--ds-gray-600:#444;--ds-gray-700:#333;--ds-gray-800:#111;--ds-gray-900:#000;--ds-gray-1000:#000;--ds-red-100:#fff0f0;--ds-red-200:#ffebeb;--ds-red-300:#ffe6e6;--ds-red-700:#e5484d;--ds-red-800:#da2f35;--ds-red-900:#cb2a2f;--ds-green-100:#effbef;--ds-green-200:#ebfaeb;--ds-green-300:#daf6da;--ds-green-700:#45a557;--ds-green-800:#398e4a;--ds-green-900:#297a3a;--ds-amber-100:#fff6e6;--ds-amber-200:#fff4d6;--ds-amber-300:#fef0cd;--ds-amber-700:#ffb224;--ds-amber-800:#ff990a;--ds-amber-900:#a35200;--ds-blue-700:#0070f3;--accents-1:var(--ds-gray-100);--accents-2:var(--ds-gray-200);--accents-3:var(--ds-gray-300);--accents-4:var(--ds-gray-400);--accents-5:var(--ds-gray-500);--accents-6:var(--ds-gray-600);--accents-7:var(--ds-gray-700);--accents-8:var(--ds-gray-800);--border:var(--accents-2);--muted:var(--accents-1);--muted-foreground:var(--accents-5);--primary:var(--foreground);--success:var(--ds-green-700);--error:var(--ds-red-800);--warning:var(--ds-amber-700);--lightningcss-light:initial;--lightningcss-dark: ;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}[data-theme=dark]{--background:#000;--foreground:#fff;--ds-gray-100:#111;--ds-gray-200:#333;--ds-gray-300:#444;--ds-gray-400:#666;--ds-gray-500:#888;--ds-gray-600:#999;--ds-gray-700:#eaeaea;--ds-gray-800:#fafafa;--ds-gray-900:#fff;--ds-gray-1000:#fff;--ds-red-100:#2a1314;--ds-red-200:#3c1618;--ds-red-300:#561a1e;--ds-red-700:#e5484d;--ds-red-800:#d93036;--ds-red-900:#ff6166;--ds-green-100:#0b2212;--ds-green-200:#0f2e18;--ds-green-300:#12361b;--ds-green-700:#45a557;--ds-green-800:#398e4a;--ds-green-900:#62c073;--ds-amber-100:#291800;--ds-amber-200:#331b00;--ds-amber-300:#4d2a00;--ds-amber-700:#ffb224;--ds-amber-800:#ff990a;--ds-amber-900:#f2a20d;--ds-blue-700:#0070f3;--accents-1:var(--ds-gray-100);--accents-2:var(--ds-gray-200);--accents-3:var(--ds-gray-300);--accents-4:var(--ds-gray-400);--accents-5:var(--ds-gray-500);--accents-6:var(--ds-gray-600);--accents-7:var(--ds-gray-700);--accents-8:var(--ds-gray-800);--border:var(--accents-2);--muted:var(--accents-1);--muted-foreground:var(--accents-5);--primary:var(--foreground);--success:var(--ds-green-700);--error:var(--ds-red-800);--warning:var(--ds-amber-700);--lightningcss-light: ;--lightningcss-dark:initial;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}*{box-sizing:border-box}body{background:var(--background);min-height:100vh;color:var(--foreground);margin:0;font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;overflow:hidden}.ant-input-affix-wrapper:focus,.ant-input-affix-wrapper-focused{border-color:var(--foreground);box-shadow:0 0 0 1px var(--foreground)}.ant-tree .ant-tree-node-content-wrapper.ant-tree-node-selected{transition:none}@keyframes status-flash{0%{filter:brightness()}5%{filter:brightness(2.5)}15%{filter:brightness(1.8)}40%{filter:brightness(1.3)}to{filter:brightness()}}.status-icon-flash{animation:1.5s ease-out status-flash}@keyframes progress-segment-flash{0%{filter:brightness();box-shadow:none}10%{filter:brightness(2.5);box-shadow:0 0 8px 1px}to{filter:brightness();box-shadow:none}}.progress-flash-active{animation:1s cubic-bezier(.4,0,.2,1) progress-segment-flash}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/*! For license information please see 565.226c9ef5.js.LICENSE.txt */
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/*! For license information please see lib-react.97ee79b0.js.LICENSE.txt */
|