diff --git a/tests/runner-e2e/current-ui-controls.spec.ts b/tests/runner-e2e/current-ui-controls.spec.ts
new file mode 100644
index 00000000000..1071f312a60
--- /dev/null
+++ b/tests/runner-e2e/current-ui-controls.spec.ts
@@ -0,0 +1,31 @@
+import { expect, test } from "@playwright/test";
+import { newProjectButton } from "./product-journeys/project-context-flow.js";
+import { storyArtifactDownloadLink } from "./story-artifact.js";
+
+const id = "11111111-2222-4333-8444-555555555555";
+
+test("finds the attachment download link in legacy and Artifacts V1 markup only", async ({ page }) => {
+ const markup = {
+ legacy: `Download`,
+ // Artifacts V1: an icon-only "Open raw" link precedes the icon-only download link.
+ v1: `slugify.zip`
+ + ``,
+ none: `Open`
+ + `Download`,
+ };
+ for (const kind of ["legacy", "v1"] as const) {
+ await page.setContent(markup[kind]);
+ await expect(storyArtifactDownloadLink(page, id)).toHaveAttribute("href", `/api/attachments/${id}/content?download=1`);
+ }
+ await page.setContent(markup.none);
+ await expect(storyArtifactDownloadLink(page, id)).toHaveCount(0);
+});
+
+test("opens new-project creation with the previous or current toolbar label only", async ({ page }) => {
+ for (const label of ["Add Project", "Create project"]) {
+ await page.setContent(``);
+ await expect(newProjectButton(page)).toHaveText(label);
+ }
+ await page.setContent(``);
+ await expect(newProjectButton(page)).toHaveCount(0);
+});
diff --git a/tests/runner-e2e/playwright-support.config.ts b/tests/runner-e2e/playwright-support.config.ts
index 3e2887eed4c..706d0d71ea1 100644
--- a/tests/runner-e2e/playwright-support.config.ts
+++ b/tests/runner-e2e/playwright-support.config.ts
@@ -3,7 +3,7 @@ import { defineConfig } from "@playwright/test";
/** Browser-only harness regressions: no Paperclip instance or provider credentials. */
export default defineConfig({
testDir: ".",
- testMatch: ["hire-delegate-composer.spec.ts", "question-resume-ui.spec.ts", "clarification-readback.spec.ts", "project-context-readback.spec.ts", "skill-studio-flow.spec.ts", "native-task-reply.spec.ts", "screenshot-readiness.spec.ts", "service-worker-reload.spec.ts", "lost-send.spec.ts", "chat-restart.spec.ts", "continuation-approval.spec.ts", "settings-toggle.spec.ts", "browser-bootstrap-diagnostics.spec.ts", "service-worker-module-loading.spec.ts", "browser-bootstrap-recovery.spec.ts", "blocker-input.spec.ts", "native-blocker-visible.spec.ts", "copilot-permission-card.spec.ts", "kimi-permission-card.spec.ts", "github-review-check.spec.ts", "antigravity-journey-browser.spec.ts", "task-search-filter.spec.ts", "task-document-navigation.spec.ts"],
+ testMatch: ["hire-delegate-composer.spec.ts", "question-resume-ui.spec.ts", "clarification-readback.spec.ts", "project-context-readback.spec.ts", "skill-studio-flow.spec.ts", "native-task-reply.spec.ts", "screenshot-readiness.spec.ts", "service-worker-reload.spec.ts", "lost-send.spec.ts", "chat-restart.spec.ts", "continuation-approval.spec.ts", "settings-toggle.spec.ts", "browser-bootstrap-diagnostics.spec.ts", "service-worker-module-loading.spec.ts", "browser-bootstrap-recovery.spec.ts", "blocker-input.spec.ts", "native-blocker-visible.spec.ts", "copilot-permission-card.spec.ts", "kimi-permission-card.spec.ts", "github-review-check.spec.ts", "antigravity-journey-browser.spec.ts", "task-search-filter.spec.ts", "task-document-navigation.spec.ts", "current-ui-controls.spec.ts"],
workers: 1,
retries: 0,
timeout: 10_000,
diff --git a/tests/runner-e2e/product-journeys/project-context-flow.ts b/tests/runner-e2e/product-journeys/project-context-flow.ts
index ae83a781a5c..493723a6797 100644
--- a/tests/runner-e2e/product-journeys/project-context-flow.ts
+++ b/tests/runner-e2e/product-journeys/project-context-flow.ts
@@ -1,9 +1,14 @@
-import { expect } from "@playwright/test";
+import { expect, type Page } from "@playwright/test";
import { createTaskThroughUi, withBrowserEvent } from "../user-actions.js";
import { projectContextEvidencePasses, type ProjectContextObservation } from "./project-context-evidence.js";
import { openProjectTaskThroughList, reopenProjectThroughList } from "./project-context-readback.js";
import { JourneyError, type JourneyContext, type JourneyEvidence, type TargetSession } from "./types.js";
+/** The toolbar action was renamed from "Add Project" to "Create project"; accept both deployed labels. */
+export function newProjectButton(page: Page) {
+ return page.getByRole("button", { name: /^(?:Add Project|Create project)$/ }).first();
+}
+
export async function projectTaskContext(context: JourneyContext, session: TargetSession, campaignId: string, evidence: JourneyEvidence) {
const { page, api, company } = context;
const projectName = `Journey QA project ${campaignId}`;
@@ -11,7 +16,7 @@ export async function projectTaskContext(context: JourneyContext, session: Targe
const prompt = `Keep this task assigned to me in ${projectName}. I will review it myself.`;
const journeyId = "project-task-context" as const;
await page.goto(`${api.origin}/${company.issuePrefix}/projects`);
- await page.getByRole("button", { name: "Add Project", exact: true }).first().click();
+ await newProjectButton(page).click();
const dialog = page.getByRole("dialog");
await dialog.getByRole("textbox", { name: "Project name", exact: true }).fill(projectName);
await session.own({ kind: "project", name: projectName, journeyId });
diff --git a/tests/runner-e2e/story-artifact.ts b/tests/runner-e2e/story-artifact.ts
index de2e411903e..fa6c7c91700 100644
--- a/tests/runner-e2e/story-artifact.ts
+++ b/tests/runner-e2e/story-artifact.ts
@@ -34,11 +34,18 @@ export async function runArtifactOracle(file: string | null, mode: "base" | "sep
}
/** Download through the visible task control; attachment metadata is not a content oracle. */
+/** Older attachment cards label the link "Download"; Artifacts V1 rows use an icon link named by aria-label. */
+export function storyArtifactDownloadLink(page: Page, attachmentId: string) {
+ const content = `a[href*="/api/attachments/${attachmentId}/content"]`;
+ return page.locator(content).filter({ hasText: /Download/i })
+ .or(page.locator(`${content}[aria-label^="Download" i]`)).first();
+}
+
export async function downloadStoryArtifact(input: {
page: Page; attachmentId: string; directory: string; phase: string;
mode: "base" | "separator" | "max-length"; deadlineAt: number;
}) {
- const link = input.page.locator(`a[href*="/api/attachments/${input.attachmentId}/content"]`).filter({ hasText: /Download/i }).first();
+ const link = storyArtifactDownloadLink(input.page, input.attachmentId);
await expect(link).toBeVisible({ timeout: 30_000 });
const [download] = await Promise.all([input.page.waitForEvent("download", { timeout: 30_000 }), link.click()]);
await mkdir(input.directory, { recursive: true, mode: 0o700 });