Add mass-import feature for customer data including UI, parsing enhancements, and E2E testing
This commit is contained in:
@@ -13,6 +13,7 @@ const runtimeNamespace = String(process.env.PLAYWRIGHT_ARTIFACT_NAMESPACE || `po
|
||||
.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
||||
const pidFile = path.resolve(process.cwd(), "output/playwright", `dev-server-${runtimeNamespace}.json`);
|
||||
const serverOutputLimit = 80;
|
||||
const activeOutputReaders = [];
|
||||
// Hardlinked Windows worktrees can break Vite's bundled config temp paths during Playwright boot.
|
||||
const viteDevArgs = [
|
||||
"run",
|
||||
@@ -289,12 +290,107 @@ async function warmUpAsset(url, expectedContentType, timeoutMs = 120_000) {
|
||||
throw new Error(`Timed out warming Playwright dev asset ${url}.`);
|
||||
}
|
||||
|
||||
async function fetchWarmedAsset(url, expectedContentType, timeoutMs = 120_000) {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
const perRequestTimeoutMs = 10_000;
|
||||
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
redirect: "manual",
|
||||
signal: AbortSignal.timeout(perRequestTimeoutMs),
|
||||
});
|
||||
const contentType = response.headers.get("content-type") || "";
|
||||
|
||||
if (response.ok && contentType.toLowerCase().includes(expectedContentType)) {
|
||||
return await response.text();
|
||||
}
|
||||
} catch {
|
||||
// keep polling until the Vite transform is ready
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
}
|
||||
|
||||
throw new Error(`Timed out warming Playwright dev asset ${url}.`);
|
||||
}
|
||||
|
||||
function extractModuleImports(source) {
|
||||
const specifiers = new Set();
|
||||
const importPattern =
|
||||
/\bimport(?:\s+[^"'()]+?\s+from\s+)?["']([^"']+)["']|\bimport\(\s*["']([^"']+)["']\s*\)/g;
|
||||
|
||||
for (const match of source.matchAll(importPattern)) {
|
||||
const specifier = match[1] || match[2];
|
||||
if (!specifier) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (specifier.startsWith("/src/") || specifier.startsWith("/node_modules/.vite/deps/")) {
|
||||
specifiers.add(specifier);
|
||||
}
|
||||
}
|
||||
|
||||
return [...specifiers];
|
||||
}
|
||||
|
||||
function resolveExpectedContentType(specifier) {
|
||||
const cleanSpecifier = specifier.split("?")[0];
|
||||
|
||||
if (cleanSpecifier.endsWith(".css")) {
|
||||
return "css";
|
||||
}
|
||||
|
||||
if (cleanSpecifier.endsWith(".json")) {
|
||||
return "json";
|
||||
}
|
||||
|
||||
return "javascript";
|
||||
}
|
||||
|
||||
async function warmModuleGraph(entryUrl, { depth = 2, timeoutMs = 120_000 } = {}) {
|
||||
const queue = [{ url: entryUrl, depth: 0 }];
|
||||
const visited = new Set();
|
||||
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift();
|
||||
if (!current || visited.has(current.url)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
visited.add(current.url);
|
||||
|
||||
const source = await fetchWarmedAsset(current.url, "javascript", timeoutMs);
|
||||
if (current.depth >= depth) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const specifier of extractModuleImports(source)) {
|
||||
const expectedContentType = resolveExpectedContentType(specifier);
|
||||
const importUrl = new URL(specifier, current.url).toString();
|
||||
|
||||
if (expectedContentType === "javascript" && specifier.startsWith("/src/")) {
|
||||
queue.push({
|
||||
url: importUrl,
|
||||
depth: current.depth + 1,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
await warmUpAsset(importUrl, expectedContentType, timeoutMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function warmUpDevServer(url) {
|
||||
const warmupTargets = await buildWarmupTargets();
|
||||
|
||||
for (const target of warmupTargets) {
|
||||
await warmUpAsset(new URL(target.pathname, url).toString(), target.expectedContentType);
|
||||
}
|
||||
|
||||
await warmModuleGraph(new URL("/src/main.js", url).toString());
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
export default async function globalSetup() {
|
||||
@@ -339,6 +435,7 @@ export default async function globalSetup() {
|
||||
const stderrLines = [];
|
||||
const stdoutReader = captureProcessOutput(serverProcess.stdout, stdoutLines);
|
||||
const stderrReader = captureProcessOutput(serverProcess.stderr, stderrLines);
|
||||
activeOutputReaders.push(stdoutReader, stderrReader);
|
||||
serverProcess.unref();
|
||||
|
||||
await fs.writeFile(pidFile, JSON.stringify({ pid: serverProcess.pid, port: devPort }), "utf8");
|
||||
@@ -349,8 +446,5 @@ export default async function globalSetup() {
|
||||
} catch (error) {
|
||||
await killProcessTree(serverProcess.pid);
|
||||
throw error;
|
||||
} finally {
|
||||
stdoutReader.close();
|
||||
stderrReader.close();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user