@le-space/playwright 0.6.30 → 0.6.31
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/chunk-YAODEYKV.js +66 -0
- package/cleanup-cli.d.ts +10 -0
- package/cleanup-cli.js +150 -0
- package/index.d.ts +12 -11
- package/index.js +9 -58
- package/package.json +6 -3
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// src/aleph-instance.ts
|
|
2
|
+
var SUPPORTED_ALEPH_API_HOSTS = ["https://api2.aleph.im", "https://api.aleph.im"];
|
|
3
|
+
var DEFAULT_ALEPH_SCHEDULER_URL = "https://scheduler.api.aleph.cloud";
|
|
4
|
+
var API3_HOST_PATTERN = /(^|\.)api3\.aleph\.im$/iu;
|
|
5
|
+
function delay(ms) {
|
|
6
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
8
|
+
function normalizeApiOrigin(value) {
|
|
9
|
+
try {
|
|
10
|
+
const url = new URL(value);
|
|
11
|
+
if (API3_HOST_PATTERN.test(url.hostname)) return null;
|
|
12
|
+
return url.origin;
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function resolveAlephApiHosts(candidates) {
|
|
18
|
+
const allowed = new Set((candidates ?? SUPPORTED_ALEPH_API_HOSTS).map(normalizeApiOrigin).filter((value) => value != null));
|
|
19
|
+
const selected = SUPPORTED_ALEPH_API_HOSTS.filter((host) => allowed.has(host));
|
|
20
|
+
return selected.length > 0 ? [...selected] : [...SUPPORTED_ALEPH_API_HOSTS];
|
|
21
|
+
}
|
|
22
|
+
async function waitForAlephInstanceDeletion(options) {
|
|
23
|
+
const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
24
|
+
if (!fetchImpl) throw new Error("A fetch implementation is required for cleanup verification");
|
|
25
|
+
const deadline = Date.now() + (options.timeoutMs ?? 5 * 6e4);
|
|
26
|
+
const hosts = resolveAlephApiHosts(options.apiHosts);
|
|
27
|
+
const schedulerUrl = new URL(`/api/v0/allocation/${options.instanceHash}`, options.schedulerUrl ?? DEFAULT_ALEPH_SCHEDULER_URL);
|
|
28
|
+
let lastSummary = "Deletion has not been observed yet.";
|
|
29
|
+
while (Date.now() < deadline) {
|
|
30
|
+
const observations = [];
|
|
31
|
+
let replicasForgotten = true;
|
|
32
|
+
for (const apiHost of hosts) {
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetchImpl(new URL(`/api/v0/messages/${options.instanceHash}`, apiHost), { cache: "no-cache" });
|
|
35
|
+
const payload = await response.json().catch(() => null);
|
|
36
|
+
const forgotten = payload?.status === "forgotten" || Boolean(payload?.forgotten_by?.length);
|
|
37
|
+
replicasForgotten &&= forgotten;
|
|
38
|
+
observations.push(`${apiHost}: ${forgotten ? "forgotten" : payload?.status ?? `HTTP ${response.status}`}`);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
replicasForgotten = false;
|
|
41
|
+
observations.push(`${apiHost}: ${error instanceof Error ? error.message : String(error)}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
let unallocated = false;
|
|
45
|
+
try {
|
|
46
|
+
const response = await fetchImpl(schedulerUrl, { cache: "no-cache" });
|
|
47
|
+
const payload = await response.json().catch(() => null);
|
|
48
|
+
unallocated = response.status === 404 || payload?.error === "VM is not allocated to any node";
|
|
49
|
+
observations.push(`scheduler: ${unallocated ? "unallocated" : `HTTP ${response.status}`}`);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
observations.push(`scheduler: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
53
|
+
lastSummary = observations.join("; ");
|
|
54
|
+
if (replicasForgotten && unallocated) return lastSummary;
|
|
55
|
+
await delay(options.pollIntervalMs ?? 2e3);
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`Aleph INSTANCE ${options.instanceHash} was not deleted within ${options.timeoutMs ?? 5 * 6e4}ms: ${lastSummary}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export {
|
|
61
|
+
SUPPORTED_ALEPH_API_HOSTS,
|
|
62
|
+
DEFAULT_ALEPH_SCHEDULER_URL,
|
|
63
|
+
delay,
|
|
64
|
+
resolveAlephApiHosts,
|
|
65
|
+
waitForAlephInstanceDeletion
|
|
66
|
+
};
|
package/cleanup-cli.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
declare function parseCleanupCliArgs(argv: readonly string[], env: NodeJS.ProcessEnv): {
|
|
3
|
+
instanceHash: string;
|
|
4
|
+
apiHosts: string[];
|
|
5
|
+
evidencePath: string;
|
|
6
|
+
reason: string;
|
|
7
|
+
};
|
|
8
|
+
declare function runCleanupCli(argv?: readonly string[], env?: NodeJS.ProcessEnv): Promise<void>;
|
|
9
|
+
|
|
10
|
+
export { parseCleanupCliArgs, runCleanupCli };
|
package/cleanup-cli.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
resolveAlephApiHosts,
|
|
4
|
+
waitForAlephInstanceDeletion
|
|
5
|
+
} from "./chunk-YAODEYKV.js";
|
|
6
|
+
|
|
7
|
+
// src/cleanup-cli.ts
|
|
8
|
+
import { createHash } from "crypto";
|
|
9
|
+
import { appendFile, mkdir, writeFile } from "fs/promises";
|
|
10
|
+
import { dirname } from "path";
|
|
11
|
+
import process from "process";
|
|
12
|
+
import { eraseInstanceOnCrn, forgetAlephMessages } from "@le-space/core";
|
|
13
|
+
function parseCleanupCliArgs(argv, env) {
|
|
14
|
+
const flags = /* @__PURE__ */ new Map();
|
|
15
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
16
|
+
const argument = argv[index];
|
|
17
|
+
if (!argument.startsWith("--")) continue;
|
|
18
|
+
const equals = argument.indexOf("=");
|
|
19
|
+
if (equals > -1) {
|
|
20
|
+
flags.set(argument.slice(2, equals), argument.slice(equals + 1));
|
|
21
|
+
} else {
|
|
22
|
+
const value = argv[index + 1];
|
|
23
|
+
if (value == null || value.startsWith("--")) throw new Error(`Missing value for --${argument.slice(2)}`);
|
|
24
|
+
flags.set(argument.slice(2), value);
|
|
25
|
+
index += 1;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const instanceHash = (flags.get("instance-hash") ?? env.ALEPH_PLAYWRIGHT_INSTANCE_HASH ?? "").trim();
|
|
29
|
+
if (!/^[a-f0-9]{64}$/iu.test(instanceHash)) {
|
|
30
|
+
throw new Error("Cleanup requires one exact INSTANCE hash (--instance-hash or ALEPH_PLAYWRIGHT_INSTANCE_HASH)");
|
|
31
|
+
}
|
|
32
|
+
const hostCandidates = (flags.get("api-hosts") ?? env.ALEPH_VM_API_HOSTS ?? "").split(/[\s,]+/u).filter(Boolean);
|
|
33
|
+
return {
|
|
34
|
+
instanceHash,
|
|
35
|
+
apiHosts: resolveAlephApiHosts(hostCandidates.length > 0 ? hostCandidates : void 0),
|
|
36
|
+
evidencePath: (flags.get("evidence-path") ?? env.EVIDENCE_PATH ?? "").trim() || `playwright-runner-cleanup-${instanceHash.slice(0, 12)}.json`,
|
|
37
|
+
reason: (flags.get("reason") ?? "").trim() || `Ephemeral Playwright runner cleanup for ${instanceHash}`
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function importOptional(specifier) {
|
|
41
|
+
return import(specifier);
|
|
42
|
+
}
|
|
43
|
+
async function createSigner(privateKey) {
|
|
44
|
+
const normalizedKey = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
|
|
45
|
+
try {
|
|
46
|
+
const { Wallet } = await importOptional("ethers");
|
|
47
|
+
const wallet = new Wallet(normalizedKey);
|
|
48
|
+
return { address: await wallet.getAddress(), sign: (payload) => wallet.signMessage(payload) };
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error?.code !== "ERR_MODULE_NOT_FOUND") throw error;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const { privateKeyToAccount } = await importOptional("viem/accounts");
|
|
54
|
+
const account = privateKeyToAccount(normalizedKey);
|
|
55
|
+
return { address: account.address, sign: (payload) => account.signMessage({ message: payload }) };
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error?.code !== "ERR_MODULE_NOT_FOUND") throw error;
|
|
58
|
+
}
|
|
59
|
+
throw new Error('playwright-runner-cleanup needs either "ethers" or "viem" to sign the FORGET message');
|
|
60
|
+
}
|
|
61
|
+
async function runCleanupCli(argv = process.argv.slice(2), env = process.env) {
|
|
62
|
+
const options = parseCleanupCliArgs(argv, env);
|
|
63
|
+
const privateKey = env.ALEPH_PRIVATE_KEY?.trim();
|
|
64
|
+
if (!privateKey) throw new Error("ALEPH_PRIVATE_KEY is required");
|
|
65
|
+
const identity = await createSigner(privateKey);
|
|
66
|
+
const signer = (_sender, payload) => identity.sign(payload);
|
|
67
|
+
const fetchImpl = globalThis.fetch.bind(globalThis);
|
|
68
|
+
let erase;
|
|
69
|
+
for (const apiHost of options.apiHosts) {
|
|
70
|
+
try {
|
|
71
|
+
erase = await eraseInstanceOnCrn({
|
|
72
|
+
sender: identity.address,
|
|
73
|
+
signer,
|
|
74
|
+
instanceHash: options.instanceHash,
|
|
75
|
+
fetch: fetchImpl,
|
|
76
|
+
apiHost
|
|
77
|
+
});
|
|
78
|
+
break;
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
let forget = null;
|
|
83
|
+
for (const apiHost of options.apiHosts) {
|
|
84
|
+
try {
|
|
85
|
+
forget = await forgetAlephMessages({
|
|
86
|
+
sender: identity.address,
|
|
87
|
+
hashes: [options.instanceHash],
|
|
88
|
+
reason: options.reason,
|
|
89
|
+
signer,
|
|
90
|
+
hasher: (content) => createHash("sha256").update(content).digest("hex"),
|
|
91
|
+
fetch: fetchImpl,
|
|
92
|
+
apiHost,
|
|
93
|
+
sync: true
|
|
94
|
+
});
|
|
95
|
+
if (forget.status === "rejected") throw new Error("FORGET rejected");
|
|
96
|
+
break;
|
|
97
|
+
} catch {
|
|
98
|
+
forget = null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!forget) throw new Error(`Owner-signed FORGET failed for ${options.instanceHash}`);
|
|
102
|
+
const verification = await waitForAlephInstanceDeletion({
|
|
103
|
+
instanceHash: options.instanceHash,
|
|
104
|
+
apiHosts: options.apiHosts,
|
|
105
|
+
fetch: fetchImpl
|
|
106
|
+
});
|
|
107
|
+
await mkdir(dirname(options.evidencePath) || ".", { recursive: true });
|
|
108
|
+
await writeFile(
|
|
109
|
+
options.evidencePath,
|
|
110
|
+
`${JSON.stringify(
|
|
111
|
+
{
|
|
112
|
+
instanceHash: options.instanceHash,
|
|
113
|
+
owner: identity.address,
|
|
114
|
+
apiHosts: options.apiHosts,
|
|
115
|
+
erase,
|
|
116
|
+
forget: forget.itemHash,
|
|
117
|
+
verification
|
|
118
|
+
},
|
|
119
|
+
null,
|
|
120
|
+
2
|
|
121
|
+
)}
|
|
122
|
+
`
|
|
123
|
+
);
|
|
124
|
+
if (env.GITHUB_OUTPUT) await appendFile(env.GITHUB_OUTPUT, `evidence_path=${options.evidencePath}
|
|
125
|
+
`);
|
|
126
|
+
if (env.GITHUB_STEP_SUMMARY) {
|
|
127
|
+
await appendFile(
|
|
128
|
+
env.GITHUB_STEP_SUMMARY,
|
|
129
|
+
`
|
|
130
|
+
## Aleph Playwright cleanup
|
|
131
|
+
|
|
132
|
+
- Exact INSTANCE: \`${options.instanceHash}\`
|
|
133
|
+
- Runtime erase: \`${erase?.status ?? "unavailable"}\`
|
|
134
|
+
- Owner FORGET: \`${forget.status}\`
|
|
135
|
+
- Verification: ${verification}
|
|
136
|
+
`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
var invokedDirectly = process.argv[1]?.endsWith("cleanup-cli.ts") || process.argv[1]?.endsWith("cleanup-cli.js") || process.argv[1]?.endsWith("playwright-runner-cleanup");
|
|
141
|
+
if (invokedDirectly) {
|
|
142
|
+
runCleanupCli().catch((error) => {
|
|
143
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
144
|
+
process.exitCode = 1;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
export {
|
|
148
|
+
parseCleanupCliArgs,
|
|
149
|
+
runCleanupCli
|
|
150
|
+
};
|
package/index.d.ts
CHANGED
|
@@ -3,9 +3,19 @@ import { Browser, Page, Locator, BrowserContext } from '@playwright/test';
|
|
|
3
3
|
import { RelayBootstrapPostRecord, RelayBootstrapContent, fetchAlephBootstrapPosts } from '@le-space/aleph-bootstrap';
|
|
4
4
|
import { eraseInstanceOnCrn, forgetAlephMessages } from '@le-space/core';
|
|
5
5
|
|
|
6
|
-
declare const PLAYWRIGHT_RUNNER_VERSION = "1.61.1";
|
|
7
6
|
declare const SUPPORTED_ALEPH_API_HOSTS: readonly ["https://api2.aleph.im", "https://api.aleph.im"];
|
|
8
7
|
declare const DEFAULT_ALEPH_SCHEDULER_URL = "https://scheduler.api.aleph.cloud";
|
|
8
|
+
declare function resolveAlephApiHosts(candidates?: readonly string[]): string[];
|
|
9
|
+
declare function waitForAlephInstanceDeletion(options: {
|
|
10
|
+
instanceHash: string;
|
|
11
|
+
apiHosts?: readonly string[];
|
|
12
|
+
schedulerUrl?: string;
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
pollIntervalMs?: number;
|
|
15
|
+
fetch?: typeof fetch;
|
|
16
|
+
}): Promise<string>;
|
|
17
|
+
|
|
18
|
+
declare const PLAYWRIGHT_RUNNER_VERSION = "1.61.1";
|
|
9
19
|
interface RelayWalletAccount {
|
|
10
20
|
address: string;
|
|
11
21
|
signMessage(args: {
|
|
@@ -100,7 +110,7 @@ interface WaitForPubsubSubscriberOptions {
|
|
|
100
110
|
stableForMs?: number;
|
|
101
111
|
}
|
|
102
112
|
interface RelayButtonDriverOptions {
|
|
103
|
-
launcherName?: RegExp;
|
|
113
|
+
launcherName?: string | RegExp;
|
|
104
114
|
instanceNamePlaceholder?: string;
|
|
105
115
|
sshPublicKeyPlaceholder?: string;
|
|
106
116
|
connectWalletName?: string;
|
|
@@ -176,7 +186,6 @@ interface CreateRelayTestOptions {
|
|
|
176
186
|
evidence: RelayEvidence;
|
|
177
187
|
cleanup?: Omit<CleanupRelayOptions, 'page' | 'account' | 'instanceName' | 'instanceHash'>;
|
|
178
188
|
}
|
|
179
|
-
declare function resolveAlephApiHosts(candidates?: readonly string[]): string[];
|
|
180
189
|
declare function connectAlephChromium(options: AlephRemoteBrowserOptions): Promise<Browser>;
|
|
181
190
|
declare function buildAlephCostEvidence(options: {
|
|
182
191
|
startedAt: string;
|
|
@@ -232,14 +241,6 @@ declare function waitForBootstrapRegistration(options: {
|
|
|
232
241
|
}): Promise<RelayBootstrapPostRecord>;
|
|
233
242
|
declare function selectBrowserRelayAddresses(content: Pick<RelayBootstrapContent, 'browserMultiaddrs' | 'multiaddrs'>, policy?: RelayAddressPolicy): string[];
|
|
234
243
|
declare function provisionRelay(page: Page, options: ProvisionRelayOptions): Promise<ProvisionedRelay>;
|
|
235
|
-
declare function waitForAlephInstanceDeletion(options: {
|
|
236
|
-
instanceHash: string;
|
|
237
|
-
apiHosts?: readonly string[];
|
|
238
|
-
schedulerUrl?: string;
|
|
239
|
-
timeoutMs?: number;
|
|
240
|
-
pollIntervalMs?: number;
|
|
241
|
-
fetch?: typeof fetch;
|
|
242
|
-
}): Promise<string>;
|
|
243
244
|
declare function waitForAlephMessageForgotten(options: {
|
|
244
245
|
messageHash: string;
|
|
245
246
|
apiHosts?: readonly string[];
|
package/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_ALEPH_SCHEDULER_URL,
|
|
3
|
+
SUPPORTED_ALEPH_API_HOSTS,
|
|
4
|
+
delay,
|
|
5
|
+
resolveAlephApiHosts,
|
|
6
|
+
waitForAlephInstanceDeletion
|
|
7
|
+
} from "./chunk-YAODEYKV.js";
|
|
8
|
+
|
|
1
9
|
// src/index.ts
|
|
2
10
|
import { createHash } from "crypto";
|
|
3
11
|
import { appendFile, mkdir, writeFile } from "fs/promises";
|
|
@@ -6,23 +14,6 @@ import { fetchAlephBootstrapPosts } from "@le-space/aleph-bootstrap";
|
|
|
6
14
|
import { eraseInstanceOnCrn, forgetAlephMessages } from "@le-space/core";
|
|
7
15
|
import { test as playwrightTest } from "@playwright/test";
|
|
8
16
|
var PLAYWRIGHT_RUNNER_VERSION = "1.61.1";
|
|
9
|
-
var SUPPORTED_ALEPH_API_HOSTS = ["https://api2.aleph.im", "https://api.aleph.im"];
|
|
10
|
-
var DEFAULT_ALEPH_SCHEDULER_URL = "https://scheduler.api.aleph.cloud";
|
|
11
|
-
var API3_HOST_PATTERN = /(^|\.)api3\.aleph\.im$/iu;
|
|
12
|
-
function normalizeApiOrigin(value) {
|
|
13
|
-
try {
|
|
14
|
-
const url = new URL(value);
|
|
15
|
-
if (API3_HOST_PATTERN.test(url.hostname)) return null;
|
|
16
|
-
return url.origin;
|
|
17
|
-
} catch {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function resolveAlephApiHosts(candidates) {
|
|
22
|
-
const allowed = new Set((candidates ?? SUPPORTED_ALEPH_API_HOSTS).map(normalizeApiOrigin).filter((value) => value != null));
|
|
23
|
-
const selected = SUPPORTED_ALEPH_API_HOSTS.filter((host) => allowed.has(host));
|
|
24
|
-
return selected.length > 0 ? [...selected] : [...SUPPORTED_ALEPH_API_HOSTS];
|
|
25
|
-
}
|
|
26
17
|
function requireFiniteNumber(value, label) {
|
|
27
18
|
const number = Number(value);
|
|
28
19
|
if (!Number.isFinite(number)) throw new Error(`${label} must be a finite number`);
|
|
@@ -209,7 +200,7 @@ var RelayButtonDriver = class {
|
|
|
209
200
|
constructor(page, options = {}) {
|
|
210
201
|
this.page = page;
|
|
211
202
|
this.options = {
|
|
212
|
-
launcherName: options.launcherName ??
|
|
203
|
+
launcherName: options.launcherName ?? "Relay Button",
|
|
213
204
|
instanceNamePlaceholder: options.instanceNamePlaceholder ?? "Instance name",
|
|
214
205
|
sshPublicKeyPlaceholder: options.sshPublicKeyPlaceholder ?? "SSH public key",
|
|
215
206
|
connectWalletName: options.connectWalletName ?? "Connect MetaMask",
|
|
@@ -298,9 +289,6 @@ async function waitForDeploymentUi(page, instanceName, timeoutMs) {
|
|
|
298
289
|
throw new Error(`Relay Button deployment failed: ${result.message}`);
|
|
299
290
|
}
|
|
300
291
|
}
|
|
301
|
-
function delay(ms) {
|
|
302
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
303
|
-
}
|
|
304
292
|
async function findAlephInstanceHash(options) {
|
|
305
293
|
const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
306
294
|
if (!fetchImpl) throw new Error("A fetch implementation is required for INSTANCE lookup");
|
|
@@ -452,43 +440,6 @@ async function provisionRelay(page, options) {
|
|
|
452
440
|
driver
|
|
453
441
|
};
|
|
454
442
|
}
|
|
455
|
-
async function waitForAlephInstanceDeletion(options) {
|
|
456
|
-
const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
457
|
-
if (!fetchImpl) throw new Error("A fetch implementation is required for cleanup verification");
|
|
458
|
-
const deadline = Date.now() + (options.timeoutMs ?? 5 * 6e4);
|
|
459
|
-
const hosts = resolveAlephApiHosts(options.apiHosts);
|
|
460
|
-
const schedulerUrl = new URL(`/api/v0/allocation/${options.instanceHash}`, options.schedulerUrl ?? DEFAULT_ALEPH_SCHEDULER_URL);
|
|
461
|
-
let lastSummary = "Deletion has not been observed yet.";
|
|
462
|
-
while (Date.now() < deadline) {
|
|
463
|
-
const observations = [];
|
|
464
|
-
let replicasForgotten = true;
|
|
465
|
-
for (const apiHost of hosts) {
|
|
466
|
-
try {
|
|
467
|
-
const response = await fetchImpl(new URL(`/api/v0/messages/${options.instanceHash}`, apiHost), { cache: "no-cache" });
|
|
468
|
-
const payload = await response.json().catch(() => null);
|
|
469
|
-
const forgotten = payload?.status === "forgotten" || Boolean(payload?.forgotten_by?.length);
|
|
470
|
-
replicasForgotten &&= forgotten;
|
|
471
|
-
observations.push(`${apiHost}: ${forgotten ? "forgotten" : payload?.status ?? `HTTP ${response.status}`}`);
|
|
472
|
-
} catch (error) {
|
|
473
|
-
replicasForgotten = false;
|
|
474
|
-
observations.push(`${apiHost}: ${error instanceof Error ? error.message : String(error)}`);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
let unallocated = false;
|
|
478
|
-
try {
|
|
479
|
-
const response = await fetchImpl(schedulerUrl, { cache: "no-cache" });
|
|
480
|
-
const payload = await response.json().catch(() => null);
|
|
481
|
-
unallocated = response.status === 404 || payload?.error === "VM is not allocated to any node";
|
|
482
|
-
observations.push(`scheduler: ${unallocated ? "unallocated" : `HTTP ${response.status}`}`);
|
|
483
|
-
} catch (error) {
|
|
484
|
-
observations.push(`scheduler: ${error instanceof Error ? error.message : String(error)}`);
|
|
485
|
-
}
|
|
486
|
-
lastSummary = observations.join("; ");
|
|
487
|
-
if (replicasForgotten && unallocated) return lastSummary;
|
|
488
|
-
await delay(options.pollIntervalMs ?? 2e3);
|
|
489
|
-
}
|
|
490
|
-
throw new Error(`Aleph INSTANCE ${options.instanceHash} was not deleted within ${options.timeoutMs ?? 5 * 6e4}ms: ${lastSummary}`);
|
|
491
|
-
}
|
|
492
443
|
async function waitForAlephMessageForgotten(options) {
|
|
493
444
|
const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
494
445
|
if (!fetchImpl) throw new Error("A fetch implementation is required for cleanup verification");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@le-space/playwright",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.31",
|
|
4
4
|
"description": "Reusable Playwright fixtures and Relay Button lifecycle helpers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
"import": "./index.js"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"playwright-runner-cleanup": "./cleanup-cli.js"
|
|
17
|
+
},
|
|
15
18
|
"publishConfig": {
|
|
16
19
|
"access": "public"
|
|
17
20
|
},
|
|
@@ -24,8 +27,8 @@
|
|
|
24
27
|
"url": "https://github.com/NiKrause/relay-button/issues"
|
|
25
28
|
},
|
|
26
29
|
"dependencies": {
|
|
27
|
-
"@le-space/aleph-bootstrap": "0.6.
|
|
28
|
-
"@le-space/core": "0.6.
|
|
30
|
+
"@le-space/aleph-bootstrap": "0.6.31",
|
|
31
|
+
"@le-space/core": "0.6.31"
|
|
29
32
|
},
|
|
30
33
|
"peerDependencies": {
|
|
31
34
|
"@playwright/test": ">=1.61.1 <1.62.0"
|