Unverified 提交 0fb5773a authored 作者: keppo-bot[bot]'s avatar keppo-bot[bot] 提交者: GitHub

test: deflake context_manage E2E assertions (#3187)

## Summary - replace context_manage snapshot assertions with narrower UI and dump-path checks - wait for context rows to appear or disappear before continuing so the picker interactions are deterministic - remove the obsolete context_manage snapshot baselines ## Test plan - npm run build - PLAYWRIGHT_RETRIES=0 PLAYWRIGHT_HTML_OPEN=never npm run e2e -- e2e-tests/context_manage.spec.ts --repeat-each=10 - npm run fmt - npm run lint:fix - npm run ts - npm test #skip-bugbot <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/3187" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> --------- Co-authored-by: 's avatarWill Chen <7344640+wwwillchen@users.noreply.github.com>
上级 9bab193f
import { test } from "./helpers/test_helper";
import fs from "fs";
import { expect } from "@playwright/test";
import { test, type PageObject } from "./helpers/test_helper";
type DumpContentPart = {
text?: string;
};
type DumpMessage = {
role: string;
content: string | DumpContentPart[];
};
type DumpFileReference = {
path: string;
force?: boolean;
};
type DumpJson = {
body: {
input?: DumpMessage[];
messages?: DumpMessage[];
dyad_options?: {
enable_smart_files_context?: boolean;
smart_context_mode?: string;
files?: DumpFileReference[];
versioned_files?: {
fileReferences?: DumpFileReference[];
};
};
};
};
const FULL_CODEBASE_PATHS = [
".env.foobar",
"AI_RULES.md",
"a.ts",
"exclude/exclude.ts",
"exclude/exclude.tsx",
"manual/baz.json",
"manual/file.ts",
"manual/sub-manual/sub-manual.js",
"src/components/ui/button.tsx",
"src/components/ui/helper.ts",
"src/dir/some.css",
"src/foo.ts",
"src/sub/sub1.ts",
"src/sub/sub2.tsx",
"src/very-large-file.ts",
];
const MANUAL_SRC_CONTEXT_PATHS = [
"src/components/ui/helper.ts",
"src/foo.ts",
"src/sub/sub1.ts",
"src/sub/sub2.tsx",
"src/very-large-file.ts",
];
const MANUAL_AND_SMART_CONTEXT_PATHS = [
"a.ts",
"manual/baz.json",
"manual/file.ts",
"manual/sub-manual/sub-manual.js",
...MANUAL_SRC_CONTEXT_PATHS,
];
const FORCED_AUTO_INCLUDE_PATHS = [
"a.ts",
"manual/baz.json",
"manual/file.ts",
"manual/sub-manual/sub-manual.js",
];
const FORCED_SMART_EXCLUDE_PATHS = ["a.ts", "exclude/exclude.tsx"];
async function readDump(po: PageObject, dumpIndex = -1): Promise<DumpJson> {
await po.chatActions.waitForChatCompletion();
await expect(po.page.getByTestId("messages-list")).toContainText(
"[[dyad-dump-path=",
);
const messagesListText = await po.page
.getByTestId("messages-list")
.textContent();
const dumpPathMatches = [
...(messagesListText?.matchAll(/\[\[dyad-dump-path=([^\]]+)\]\]/g) ?? []),
];
const selectedIndex =
dumpIndex < 0 ? dumpPathMatches.length + dumpIndex : dumpIndex;
const dumpMatch = dumpPathMatches[selectedIndex];
expect(dumpMatch?.[1]).toBeTruthy();
return JSON.parse(fs.readFileSync(dumpMatch![1], "utf-8")) as DumpJson;
}
function getMessageText(content: DumpMessage["content"]): string {
if (typeof content === "string") {
return content;
}
return content.map((part) => part.text ?? "").join("\n");
}
function getIncludedFiles(dump: DumpJson): DumpFileReference[] {
const versionedFiles =
dump.body.dyad_options?.versioned_files?.fileReferences;
if (versionedFiles?.length) {
return versionedFiles;
}
const files = dump.body.dyad_options?.files;
if (files?.length) {
return files;
}
const messages = dump.body.input ?? dump.body.messages ?? [];
const messagePaths = messages.flatMap(({ content }) =>
[...getMessageText(content).matchAll(/<dyad-file path="([^"]+)">/g)].map(
(match) => match[1],
),
);
return [...new Set(messagePaths)].map((path) => ({ path, force: false }));
}
function getIncludedPaths(dump: DumpJson): string[] {
return getIncludedFiles(dump)
.map(({ path }) => path)
.sort();
}
function getForcedPaths(dump: DumpJson): string[] {
return getIncludedFiles(dump)
.filter(({ force }) => force)
.map(({ path }) => path)
.sort();
}
function expectIncludedPaths(dump: DumpJson, expectedPaths: string[]) {
expect(getIncludedPaths(dump)).toEqual([...expectedPaths].sort());
}
function expectForcedPaths(dump: DumpJson, expectedPaths: string[]) {
expect(getForcedPaths(dump)).toEqual([...expectedPaths].sort());
}
async function expectVisiblePaths(po: PageObject, paths: string[]) {
const dialog = po.page.getByRole("dialog");
for (const path of paths) {
await expect(dialog.getByText(path, { exact: true })).toBeVisible();
}
}
async function expectAbsentPaths(po: PageObject, paths: string[]) {
const dialog = po.page.getByRole("dialog");
for (const path of paths) {
await expect(dialog.getByText(path, { exact: true })).toHaveCount(0);
}
}
async function addPathAndWait(
po: PageObject,
addPath: () => Promise<void>,
path: string,
) {
await addPath();
await expectVisiblePaths(po, [path]);
}
async function removeFirstPath(po: PageObject, removeButtonTestId: string) {
const removeButtons = po.page.getByTestId(removeButtonTestId);
const initialCount = await removeButtons.count();
expect(initialCount).toBeGreaterThan(0);
await removeButtons.first().click();
await expect(removeButtons).toHaveCount(initialCount - 1);
}
async function removeAllPaths(po: PageObject, removeButtonTestId: string) {
while ((await po.page.getByTestId(removeButtonTestId).count()) > 0) {
await removeFirstPath(po, removeButtonTestId);
}
}
test("manage context - default", async ({ po }) => {
await po.setUp();
await po.importApp("context-manage");
const dialog = await po.openContextFilesPicker();
await po.snapshotDialog();
await dialog.addManualContextFile("DELETETHIS");
await dialog.removeManualContextFile();
await dialog.addManualContextFile("src/**/*.ts");
await dialog.addManualContextFile("src/sub/**");
await po.snapshotDialog();
await expect(po.page.getByRole("dialog")).toContainText(
"Dyad will use the entire codebase as context.",
);
await addPathAndWait(
po,
() => dialog.addManualContextFile("DELETETHIS"),
"DELETETHIS",
);
await removeFirstPath(po, "manual-context-files-remove-button");
await addPathAndWait(
po,
() => dialog.addManualContextFile("src/**/*.ts"),
"src/**/*.ts",
);
await addPathAndWait(
po,
() => dialog.addManualContextFile("src/sub/**"),
"src/sub/**",
);
await expectVisiblePaths(po, ["src/**/*.ts", "src/sub/**"]);
await expectAbsentPaths(po, ["DELETETHIS"]);
await expect(
po.page.getByTestId("manual-context-files-remove-button"),
).toHaveCount(2);
await dialog.close();
await po.sendPrompt("[dump]");
await po.snapshotServerDump("all-messages");
expectIncludedPaths(await readDump(po), MANUAL_SRC_CONTEXT_PATHS);
});
test("manage context - smart context", async ({ po }) => {
......@@ -27,19 +231,56 @@ test("manage context - smart context", async ({ po }) => {
await po.importApp("context-manage");
let dialog = await po.openContextFilesPicker();
await po.snapshotDialog();
await expect(po.page.getByRole("dialog")).toContainText(
"Dyad will use Smart Context to automatically find the most relevant files to use as context.",
);
await expect(po.page.getByRole("dialog")).toContainText(
"Smart Context Auto-includes",
);
await dialog.addManualContextFile("src/**/*.ts");
await dialog.addManualContextFile("src/sub/**");
await dialog.addAutoIncludeContextFile("a.ts");
await dialog.addAutoIncludeContextFile("manual/**");
await po.snapshotDialog();
await addPathAndWait(
po,
() => dialog.addManualContextFile("src/**/*.ts"),
"src/**/*.ts",
);
await addPathAndWait(
po,
() => dialog.addManualContextFile("src/sub/**"),
"src/sub/**",
);
await addPathAndWait(
po,
() => dialog.addAutoIncludeContextFile("a.ts"),
"a.ts",
);
await addPathAndWait(
po,
() => dialog.addAutoIncludeContextFile("manual/**"),
"manual/**",
);
await expectVisiblePaths(po, [
"src/**/*.ts",
"src/sub/**",
"a.ts",
"manual/**",
]);
await expect(
po.page.getByTestId("manual-context-files-remove-button"),
).toHaveCount(2);
await expect(
po.page.getByTestId("auto-include-context-files-remove-button"),
).toHaveCount(2);
await dialog.close();
await po.sendPrompt("[dump]");
await po.snapshotServerDump("request");
await po.snapshotServerDump("all-messages");
const smartContextDump = await readDump(po);
expect(smartContextDump.body.dyad_options?.enable_smart_files_context).toBe(
true,
);
expect(smartContextDump.body.dyad_options?.smart_context_mode).toBe("deep");
expectIncludedPaths(smartContextDump, MANUAL_AND_SMART_CONTEXT_PATHS);
expectForcedPaths(smartContextDump, FORCED_AUTO_INCLUDE_PATHS);
// Disabling smart context will automatically disable
// the auto-includes.
......@@ -48,16 +289,30 @@ test("manage context - smart context", async ({ po }) => {
await proModesDialog.close();
await po.sendPrompt("[dump]");
await po.snapshotServerDump("request");
const balancedDump = await readDump(po);
expect(balancedDump.body.dyad_options?.enable_smart_files_context).toBe(
false,
);
expect(balancedDump.body.dyad_options?.smart_context_mode).toBe("balanced");
expectIncludedPaths(balancedDump, MANUAL_SRC_CONTEXT_PATHS);
// Removing manual context files will result in all files being included.
dialog = await po.openContextFilesPicker();
await dialog.removeManualContextFile();
await dialog.removeManualContextFile();
await removeAllPaths(po, "manual-context-files-remove-button");
await expect(
po.page.getByTestId("manual-context-files-remove-button"),
).toHaveCount(0);
await expect(
po.page.getByTestId("auto-include-context-files-remove-button"),
).toHaveCount(0);
await dialog.close();
await po.sendPrompt("[dump]");
await po.snapshotServerDump("request");
const fullCodebaseDump = await readDump(po);
expect(fullCodebaseDump.body.dyad_options?.enable_smart_files_context).toBe(
false,
);
expectIncludedPaths(fullCodebaseDump, FULL_CODEBASE_PATHS);
});
test("manage context - smart context - auto-includes only", async ({ po }) => {
......@@ -69,16 +324,32 @@ test("manage context - smart context - auto-includes only", async ({ po }) => {
await po.importApp("context-manage");
const dialog = await po.openContextFilesPicker();
await po.snapshotDialog();
await expect(po.page.getByRole("dialog")).toContainText(
"Smart Context Auto-includes",
);
await dialog.addAutoIncludeContextFile("a.ts");
await dialog.addAutoIncludeContextFile("manual/**");
await po.snapshotDialog();
await addPathAndWait(
po,
() => dialog.addAutoIncludeContextFile("a.ts"),
"a.ts",
);
await addPathAndWait(
po,
() => dialog.addAutoIncludeContextFile("manual/**"),
"manual/**",
);
await expectVisiblePaths(po, ["a.ts", "manual/**"]);
await expect(
po.page.getByTestId("auto-include-context-files-remove-button"),
).toHaveCount(2);
await dialog.close();
await po.sendPrompt("[dump]");
await po.snapshotServerDump("request");
const dump = await readDump(po);
expect(dump.body.dyad_options?.enable_smart_files_context).toBe(true);
expect(dump.body.dyad_options?.smart_context_mode).toBe("deep");
expectForcedPaths(dump, FORCED_AUTO_INCLUDE_PATHS);
});
test("manage context - exclude paths", async ({ po }) => {
......@@ -86,32 +357,73 @@ test("manage context - exclude paths", async ({ po }) => {
await po.importApp("context-manage");
const dialog = await po.openContextFilesPicker();
await po.snapshotDialog();
await expect(po.page.getByRole("dialog")).toContainText("Exclude Paths");
// Add some include paths first
await dialog.addManualContextFile("src/**/*.ts");
await dialog.addManualContextFile("manual/**");
await addPathAndWait(
po,
() => dialog.addManualContextFile("src/**/*.ts"),
"src/**/*.ts",
);
await addPathAndWait(
po,
() => dialog.addManualContextFile("manual/**"),
"manual/**",
);
// Add exclude paths
await dialog.addExcludeContextFile("src/components/**");
await dialog.addExcludeContextFile("manual/exclude/**");
await po.snapshotDialog();
await addPathAndWait(
po,
() => dialog.addExcludeContextFile("src/components/**"),
"src/components/**",
);
await addPathAndWait(
po,
() => dialog.addExcludeContextFile("manual/exclude/**"),
"manual/exclude/**",
);
await expectVisiblePaths(po, [
"src/**/*.ts",
"manual/**",
"src/components/**",
"manual/exclude/**",
]);
await expect(
po.page.getByTestId("manual-context-files-remove-button"),
).toHaveCount(2);
await expect(
po.page.getByTestId("exclude-context-files-remove-button"),
).toHaveCount(2);
await dialog.close();
await po.sendPrompt("[dump]");
await po.snapshotServerDump("all-messages", { name: "exclude-paths-basic" });
expectIncludedPaths(await readDump(po), [
"manual/baz.json",
"manual/file.ts",
"manual/sub-manual/sub-manual.js",
"src/foo.ts",
"src/sub/sub1.ts",
"src/very-large-file.ts",
]);
// Test that exclude paths take precedence over include paths
const dialog2 = await po.openContextFilesPicker();
await dialog2.removeExcludeContextFile(); // Remove src/components/**
await dialog2.addExcludeContextFile("src/**"); // This should exclude everything from src
await po.snapshotDialog();
await removeFirstPath(po, "exclude-context-files-remove-button"); // Remove src/components/**
await addPathAndWait(
po,
() => dialog2.addExcludeContextFile("src/**"),
"src/**",
); // This should exclude everything from src
await expectVisiblePaths(po, ["manual/**", "manual/exclude/**", "src/**"]);
await expectAbsentPaths(po, ["src/components/**"]);
await dialog2.close();
await po.sendPrompt("[dump]");
await po.snapshotServerDump("all-messages", {
name: "exclude-paths-precedence",
});
expectIncludedPaths(await readDump(po), [
"manual/baz.json",
"manual/file.ts",
"manual/sub-manual/sub-manual.js",
]);
});
test("manage context - exclude paths with smart context", async ({ po }) => {
......@@ -123,24 +435,73 @@ test("manage context - exclude paths with smart context", async ({ po }) => {
await po.importApp("context-manage");
const dialog = await po.openContextFilesPicker();
await po.snapshotDialog();
await expect(po.page.getByRole("dialog")).toContainText("Exclude Paths");
// Add manual context files
await dialog.addManualContextFile("src/**/*.ts");
await dialog.addManualContextFile("manual/**");
await addPathAndWait(
po,
() => dialog.addManualContextFile("src/**/*.ts"),
"src/**/*.ts",
);
await addPathAndWait(
po,
() => dialog.addManualContextFile("manual/**"),
"manual/**",
);
// Add smart context auto-includes
await dialog.addAutoIncludeContextFile("a.ts");
await dialog.addAutoIncludeContextFile("exclude/**");
await addPathAndWait(
po,
() => dialog.addAutoIncludeContextFile("a.ts"),
"a.ts",
);
await addPathAndWait(
po,
() => dialog.addAutoIncludeContextFile("exclude/**"),
"exclude/**",
);
// Add exclude paths that should filter out some of the above
await dialog.addExcludeContextFile("src/components/**");
await dialog.addExcludeContextFile("exclude/exclude.ts");
await po.snapshotDialog();
await addPathAndWait(
po,
() => dialog.addExcludeContextFile("src/components/**"),
"src/components/**",
);
await addPathAndWait(
po,
() => dialog.addExcludeContextFile("exclude/exclude.ts"),
"exclude/exclude.ts",
);
await expectVisiblePaths(po, [
"src/**/*.ts",
"manual/**",
"a.ts",
"exclude/**",
"src/components/**",
"exclude/exclude.ts",
]);
await dialog.close();
await po.sendPrompt("[dump]");
await po.snapshotServerDump("all-messages", {
name: "exclude-paths-with-smart-context",
});
const dump = await readDump(po);
const includedPaths = getIncludedPaths(dump);
expect(dump.body.dyad_options?.enable_smart_files_context).toBe(true);
expect(includedPaths).toEqual(
expect.arrayContaining([
"a.ts",
"exclude/exclude.tsx",
"manual/baz.json",
"manual/file.ts",
"manual/sub-manual/sub-manual.js",
"src/foo.ts",
"src/sub/sub1.ts",
"src/very-large-file.ts",
]),
);
expect(getForcedPaths(dump)).toEqual(
expect.arrayContaining(FORCED_SMART_EXCLUDE_PATHS),
);
expect(includedPaths).not.toContain("exclude/exclude.ts");
expect(includedPaths).not.toContain("src/components/ui/helper.ts");
});
===
role: system
message: [[SYSTEM_MESSAGE]]
===
role: user
message: This is my codebase. <dyad-file path="manual/baz.json">
["even json is included"]
</dyad-file>
<dyad-file path="manual/file.ts">
</dyad-file>
<dyad-file path="manual/sub-manual/sub-manual.js">
</dyad-file>
<dyad-file path="src/foo.ts">
// foo.ts
</dyad-file>
<dyad-file path="src/sub/sub1.ts">
// sub/sub1.ts
</dyad-file>
<dyad-file path="src/very-large-file.ts">
// very-large-file.ts
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
</dyad-file>
===
role: assistant
message: OK, got it. I'm ready to help
===
role: user
message: [dump]
\ No newline at end of file
===
role: system
message: [[SYSTEM_MESSAGE]]
===
role: user
message: This is my codebase. <dyad-file path="manual/baz.json">
["even json is included"]
</dyad-file>
<dyad-file path="manual/file.ts">
</dyad-file>
<dyad-file path="manual/sub-manual/sub-manual.js">
</dyad-file>
===
role: assistant
message: OK, got it. I'm ready to help
===
role: user
message: [dump]
===
role: assistant
message: [[dyad-dump-path=*]]
===
role: user
message: [dump]
\ No newline at end of file
===
role: system
message: [[SYSTEM_MESSAGE]]
===
role: user
message: [dump]
\ No newline at end of file
- dialog "Codebase Context":
- heading "Codebase Context" [level=2]
- paragraph:
- text: Select the files to use as context.
- button:
- img
- textbox "src/**/*.tsx"
- button "Add"
- paragraph: Dyad will use the entire codebase as context.
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- button:
- img
- textbox "node_modules/**/*"
- button "Add"
- button "Close":
- img
\ No newline at end of file
===
role: system
message: [[SYSTEM_MESSAGE]]
===
role: user
message: This is my codebase. <dyad-file path="src/components/ui/helper.ts">
// File contents excluded from context
</dyad-file>
<dyad-file path="src/foo.ts">
// foo.ts
</dyad-file>
<dyad-file path="src/sub/sub1.ts">
// sub/sub1.ts
</dyad-file>
<dyad-file path="src/sub/sub2.tsx">
// sub/sub2.tsx
</dyad-file>
<dyad-file path="src/very-large-file.ts">
// very-large-file.ts
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
// 1234567890
</dyad-file>
===
role: assistant
message: OK, got it. I'm ready to help
===
role: user
message: [dump]
\ No newline at end of file
- dialog "Codebase Context":
- heading "Codebase Context" [level=2]
- paragraph:
- text: Select the files to use as context.
- button:
- img
- textbox "src/**/*.tsx"
- button "Add"
- button "src/**/*.ts"
- text: /4 files, ~\d+ tokens/
- button:
- img
- button "src/sub/**"
- text: /2 files, ~\d+ tokens/
- button:
- img
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- button:
- img
- textbox "node_modules/**/*"
- button "Add"
- button "Close":
- img
\ No newline at end of file
- dialog "Codebase Context":
- heading "Codebase Context" [level=2]
- paragraph:
- text: Select the files to use as context.
- button:
- img
- textbox "src/**/*.tsx"
- button "Add"
- paragraph: Dyad will use the entire codebase as context.
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- button:
- img
- textbox "node_modules/**/*"
- button "Add"
- button "Close":
- img
\ No newline at end of file
- dialog "Codebase Context":
- heading "Codebase Context" [level=2]
- paragraph:
- text: Select the files to use as context.
- button:
- img
- textbox "src/**/*.tsx"
- button "Add"
- button "src/**/*.ts"
- text: /4 files, ~\d+ tokens/
- button:
- img
- button "manual/**"
- text: /3 files, ~\d+ tokens/
- button:
- img
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- button:
- img
- textbox "node_modules/**/*"
- button "Add"
- button "src/components/**"
- text: /2 files, ~\d+ tokens/
- button:
- img
- button "manual/exclude/**"
- text: 0 files, ~0 tokens
- button:
- img
- button "Close":
- img
\ No newline at end of file
- dialog "Codebase Context":
- heading "Codebase Context" [level=2]
- paragraph:
- text: Select the files to use as context.
- button:
- img
- textbox "src/**/*.tsx"
- button "Add"
- button "src/**/*.ts"
- text: /4 files, ~\d+ tokens/
- button:
- img
- button "manual/**"
- text: /3 files, ~\d+ tokens/
- button:
- img
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- button:
- img
- textbox "node_modules/**/*"
- button "Add"
- button "manual/exclude/**"
- text: 0 files, ~0 tokens
- button:
- img
- button "src/**"
- text: /7 files, ~\d+ tokens/
- button:
- img
- button "Close":
- img
\ No newline at end of file
- dialog "Codebase Context":
- heading "Codebase Context" [level=2]
- paragraph:
- text: Select the files to use as context.
- button:
- img
- textbox "src/**/*.tsx"
- button "Add"
- paragraph: Dyad will use Smart Context to automatically find the most relevant files to use as context.
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- button:
- img
- textbox "node_modules/**/*"
- button "Add"
- heading "Smart Context Auto-includes" [level=3]
- paragraph:
- text: These files will always be included in the context.
- button:
- img
- textbox "src/**/*.config.ts"
- button "Add"
- button "Close":
- img
\ No newline at end of file
- dialog "Codebase Context":
- heading "Codebase Context" [level=2]
- paragraph:
- text: Select the files to use as context.
- button:
- img
- textbox "src/**/*.tsx"
- button "Add"
- button "src/**/*.ts"
- text: /4 files, ~\d+ tokens/
- button:
- img
- button "manual/**"
- text: /3 files, ~\d+ tokens/
- button:
- img
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- button:
- img
- textbox "node_modules/**/*"
- button "Add"
- button "exclude/exclude.ts"
- text: /1 files, ~\d+ tokens/
- button:
- img
- heading "Smart Context Auto-includes" [level=3]
- paragraph:
- text: These files will always be included in the context.
- button:
- img
- textbox "src/**/*.config.ts"
- button "Add"
- button "a.ts"
- text: /1 files, ~\d+ tokens/
- button:
- img
- button "exclude/**"
- text: /2 files, ~\d+ tokens/
- button:
- img
- button "Close":
- img
- text: ""
\ No newline at end of file
- dialog "Codebase Context":
- heading "Codebase Context" [level=2]
- paragraph:
- text: Select the files to use as context.
- button:
- img
- textbox "src/**/*.tsx"
- button "Add"
- paragraph: Dyad will use Smart Context to automatically find the most relevant files to use as context.
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- button:
- img
- textbox "node_modules/**/*"
- button "Add"
- heading "Smart Context Auto-includes" [level=3]
- paragraph:
- text: These files will always be included in the context.
- button:
- img
- textbox "src/**/*.config.ts"
- button "Add"
- button "Close":
- img
\ No newline at end of file
{
"body": {
"model": "gemini/gemini-2.5-pro",
"max_tokens": 65535,
"temperature": 0,
"messages": [
{
"role": "system",
"content": "[[SYSTEM_MESSAGE]]"
},
{
"role": "user",
"content": "[dump]"
}
],
"stream": true,
"thinking": {
"type": "enabled",
"include_thoughts": true,
"budget_tokens": 4000
},
"dyad_options": {
"versioned_files": {
"fileIdToContent": {
"[[FILE_ID_0]]": "",
"[[FILE_ID_1]]": "[\"even json is included\"]\n",
"[[FILE_ID_2]]": "/* some.css */\n",
"[[FILE_ID_3]]": "// a.ts\n",
"[[FILE_ID_4]]": "// button.tsx\n",
"[[FILE_ID_5]]": "// exclude.ts: this file is not in any of the globs\n",
"[[FILE_ID_6]]": "// exclude.tsx: this file is not in any of the globs\n",
"[[FILE_ID_7]]": "// File contents excluded from context",
"[[FILE_ID_8]]": "// foo.ts\n",
"[[FILE_ID_9]]": "// helper.ts\n",
"[[FILE_ID_10]]": "// sub/sub1.ts\n",
"[[FILE_ID_11]]": "// sub/sub2.tsx\n",
"[[FILE_ID_12]]": "// very-large-file.ts\n\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n",
"[[FILE_ID_13]]": "# AI_RULES.md\n"
},
"fileReferences": [
{
"path": ".env.foobar",
"force": false,
"fileId": "[[FILE_ID_7]]"
},
{
"path": "a.ts",
"force": true,
"fileId": "[[FILE_ID_3]]"
},
{
"path": "AI_RULES.md",
"force": false,
"fileId": "[[FILE_ID_13]]"
},
{
"path": "exclude/exclude.ts",
"force": false,
"fileId": "[[FILE_ID_5]]"
},
{
"path": "exclude/exclude.tsx",
"force": false,
"fileId": "[[FILE_ID_6]]"
},
{
"path": "manual/baz.json",
"force": true,
"fileId": "[[FILE_ID_1]]"
},
{
"path": "manual/file.ts",
"force": true,
"fileId": "[[FILE_ID_0]]"
},
{
"path": "manual/sub-manual/sub-manual.js",
"force": true,
"fileId": "[[FILE_ID_0]]"
},
{
"path": "src/components/ui/button.tsx",
"force": false,
"fileId": "[[FILE_ID_4]]"
},
{
"path": "src/components/ui/helper.ts",
"force": false,
"fileId": "[[FILE_ID_9]]"
},
{
"path": "src/dir/some.css",
"force": false,
"fileId": "[[FILE_ID_2]]"
},
{
"path": "src/foo.ts",
"force": false,
"fileId": "[[FILE_ID_8]]"
},
{
"path": "src/sub/sub1.ts",
"force": false,
"fileId": "[[FILE_ID_10]]"
},
{
"path": "src/sub/sub2.tsx",
"force": false,
"fileId": "[[FILE_ID_11]]"
},
{
"path": "src/very-large-file.ts",
"force": false,
"fileId": "[[FILE_ID_12]]"
}
],
"messageIndexToFilePathToFileId": {
"1": {}
},
"hasExternalChanges": true
},
"enable_lazy_edits": true,
"enable_smart_files_context": true,
"smart_context_mode": "deep",
"app_id": 1
}
},
"headers": {
"authorization": "Bearer testdyadkey"
}
}
\ No newline at end of file
- dialog "Codebase Context":
- heading "Codebase Context" [level=2]
- paragraph:
- text: Select the files to use as context.
- button:
- img
- textbox "src/**/*.tsx"
- button "Add"
- paragraph: Dyad will use Smart Context to automatically find the most relevant files to use as context.
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- button:
- img
- textbox "node_modules/**/*"
- button "Add"
- heading "Smart Context Auto-includes" [level=3]
- paragraph:
- text: These files will always be included in the context.
- button:
- img
- textbox "src/**/*.config.ts"
- button "Add"
- button "a.ts"
- text: /1 files, ~\d+ tokens/
- button:
- img
- button "manual/**"
- text: /3 files, ~\d+ tokens/
- button:
- img
- button "Close":
- img
\ No newline at end of file
- dialog "Codebase Context":
- heading "Codebase Context" [level=2]
- paragraph:
- text: Select the files to use as context.
- button:
- img
- textbox "src/**/*.tsx"
- button "Add"
- paragraph: Dyad will use Smart Context to automatically find the most relevant files to use as context.
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- button:
- img
- textbox "node_modules/**/*"
- button "Add"
- heading "Smart Context Auto-includes" [level=3]
- paragraph:
- text: These files will always be included in the context.
- button:
- img
- textbox "src/**/*.config.ts"
- button "Add"
- button "Close":
- img
\ No newline at end of file
{
"body": {
"model": "gemini/gemini-2.5-pro",
"max_tokens": 65535,
"temperature": 0,
"messages": [
{
"role": "system",
"content": "[[SYSTEM_MESSAGE]]"
},
{
"role": "user",
"content": "[dump]"
}
],
"stream": true,
"thinking": {
"type": "enabled",
"include_thoughts": true,
"budget_tokens": 4000
},
"dyad_options": {
"versioned_files": {
"fileIdToContent": {
"[[FILE_ID_0]]": "",
"[[FILE_ID_1]]": "[\"even json is included\"]\n",
"[[FILE_ID_2]]": "// a.ts\n",
"[[FILE_ID_3]]": "// foo.ts\n",
"[[FILE_ID_4]]": "// helper.ts\n",
"[[FILE_ID_5]]": "// sub/sub1.ts\n",
"[[FILE_ID_6]]": "// sub/sub2.tsx\n",
"[[FILE_ID_7]]": "// very-large-file.ts\n\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n"
},
"fileReferences": [
{
"path": "a.ts",
"force": true,
"fileId": "[[FILE_ID_2]]"
},
{
"path": "manual/baz.json",
"force": true,
"fileId": "[[FILE_ID_1]]"
},
{
"path": "manual/file.ts",
"force": true,
"fileId": "[[FILE_ID_0]]"
},
{
"path": "manual/sub-manual/sub-manual.js",
"force": true,
"fileId": "[[FILE_ID_0]]"
},
{
"path": "src/components/ui/helper.ts",
"force": false,
"fileId": "[[FILE_ID_4]]"
},
{
"path": "src/foo.ts",
"force": false,
"fileId": "[[FILE_ID_3]]"
},
{
"path": "src/sub/sub1.ts",
"force": false,
"fileId": "[[FILE_ID_5]]"
},
{
"path": "src/sub/sub2.tsx",
"force": false,
"fileId": "[[FILE_ID_6]]"
},
{
"path": "src/very-large-file.ts",
"force": false,
"fileId": "[[FILE_ID_7]]"
}
],
"messageIndexToFilePathToFileId": {
"1": {}
},
"hasExternalChanges": true
},
"enable_lazy_edits": true,
"enable_smart_files_context": true,
"smart_context_mode": "deep",
"app_id": 1
}
},
"headers": {
"authorization": "Bearer testdyadkey"
}
}
\ No newline at end of file
- dialog "Codebase Context":
- heading "Codebase Context" [level=2]
- paragraph:
- text: Select the files to use as context.
- button:
- img
- textbox "src/**/*.tsx"
- button "Add"
- button "src/**/*.ts"
- text: /4 files, ~\d+ tokens/
- button:
- img
- button "src/sub/**"
- text: /2 files, ~\d+ tokens/
- button:
- img
- heading "Exclude Paths" [level=3]
- paragraph:
- text: These files will be excluded from the context.
- button:
- img
- textbox "node_modules/**/*"
- button "Add"
- heading "Smart Context Auto-includes" [level=3]
- paragraph:
- text: These files will always be included in the context.
- button:
- img
- textbox "src/**/*.config.ts"
- button "Add"
- button "a.ts"
- text: /1 files, ~\d+ tokens/
- button:
- img
- button "manual/**"
- text: /3 files, ~\d+ tokens/
- button:
- img
- button "Close":
- img
\ No newline at end of file
===
role: system
message: [[SYSTEM_MESSAGE]]
===
role: user
message: [dump]
\ No newline at end of file
{
"body": {
"model": "gemini/gemini-2.5-pro",
"max_tokens": 65535,
"temperature": 0,
"messages": [
{
"role": "system",
"content": "[[SYSTEM_MESSAGE]]"
},
{
"role": "user",
"content": "[dump]"
},
{
"role": "assistant",
"content": "[[dyad-dump-path=*]]"
},
{
"role": "user",
"content": "[dump]"
}
],
"stream": true,
"thinking": {
"type": "enabled",
"include_thoughts": true,
"budget_tokens": 4000
},
"dyad_options": {
"files": [
{
"path": "src/components/ui/helper.ts",
"content": "// helper.ts\n",
"force": false
},
{
"path": "src/foo.ts",
"content": "// foo.ts\n",
"force": false
},
{
"path": "src/sub/sub1.ts",
"content": "// sub/sub1.ts\n",
"force": false
},
{
"path": "src/sub/sub2.tsx",
"content": "// sub/sub2.tsx\n",
"force": false
},
{
"path": "src/very-large-file.ts",
"content": "// very-large-file.ts\n\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n",
"force": false
}
],
"enable_lazy_edits": true,
"enable_smart_files_context": false,
"smart_context_mode": "balanced",
"app_id": 1
}
},
"headers": {
"authorization": "Bearer testdyadkey"
}
}
\ No newline at end of file
{
"body": {
"model": "gemini/gemini-2.5-pro",
"max_tokens": 65535,
"temperature": 0,
"messages": [
{
"role": "system",
"content": "[[SYSTEM_MESSAGE]]"
},
{
"role": "user",
"content": "[dump]"
},
{
"role": "assistant",
"content": "[[dyad-dump-path=*]]"
},
{
"role": "user",
"content": "[dump]"
},
{
"role": "assistant",
"content": "[[dyad-dump-path=*]]"
},
{
"role": "user",
"content": "[dump]"
}
],
"stream": true,
"thinking": {
"type": "enabled",
"include_thoughts": true,
"budget_tokens": 4000
},
"dyad_options": {
"files": [
{
"path": ".env.foobar",
"content": "// File contents excluded from context",
"force": false
},
{
"path": "a.ts",
"content": "// a.ts\n",
"force": false
},
{
"path": "AI_RULES.md",
"content": "# AI_RULES.md\n",
"force": false
},
{
"path": "exclude/exclude.ts",
"content": "// exclude.ts: this file is not in any of the globs\n",
"force": false
},
{
"path": "exclude/exclude.tsx",
"content": "// exclude.tsx: this file is not in any of the globs\n",
"force": false
},
{
"path": "manual/baz.json",
"content": "[\"even json is included\"]\n",
"force": false
},
{
"path": "manual/file.ts",
"content": "",
"force": false
},
{
"path": "manual/sub-manual/sub-manual.js",
"content": "",
"force": false
},
{
"path": "src/components/ui/button.tsx",
"content": "// button.tsx\n",
"force": false
},
{
"path": "src/components/ui/helper.ts",
"content": "// helper.ts\n",
"force": false
},
{
"path": "src/dir/some.css",
"content": "/* some.css */\n",
"force": false
},
{
"path": "src/foo.ts",
"content": "// foo.ts\n",
"force": false
},
{
"path": "src/sub/sub1.ts",
"content": "// sub/sub1.ts\n",
"force": false
},
{
"path": "src/sub/sub2.tsx",
"content": "// sub/sub2.tsx\n",
"force": false
},
{
"path": "src/very-large-file.ts",
"content": "// very-large-file.ts\n\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n",
"force": false
}
],
"enable_lazy_edits": true,
"enable_smart_files_context": false,
"smart_context_mode": "balanced",
"app_id": 1
}
},
"headers": {
"authorization": "Bearer testdyadkey"
}
}
\ No newline at end of file
......@@ -112,6 +112,7 @@ If this happens:
## Common flaky test patterns and fixes
- **After `po.importApp(...)`**: Some imports trigger an initial assistant turn (for example `minimal` generating `AI_RULES.md`) that can leave a visible `Retry` button in the chat. If the test is about a later prompt, first wait for that import-time turn to finish, then start a new chat before calling `sendPrompt()`, or helper methods that wait on `Retry` visibility may return too early.
- **Context Files Picker add/remove actions**: After clicking `Add` for manual, auto-include, or exclude paths, wait for the new row text to appear before adding or removing another path. Likewise, after clicking a remove button, wait for the row count to drop before the next click. Chained clicks can race React state updates and only fail on later `--repeat-each` runs.
- **After `page.reload()`**: Always add `await page.waitForLoadState("domcontentloaded")` before interacting with elements. Without this, the page may not have re-rendered yet.
- **Keyboard navigation events (ArrowUp/ArrowDown)**: Add `await page.waitForTimeout(100)` between sequential keyboard presses to let the UI state settle. Rapid keypresses can cause race conditions in menu navigation.
- **Navigation to tabs**: Use `await expect(link).toBeVisible({ timeout: Timeout.EXTRA_LONG })` before clicking tab links (especially in `goToAppsTab()`). Electron sidebar links can take time to render during app initialization.
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论