🐛 Make Download report.txt a keyboard button (#12723)

On the internal error page and the feedback page, "Download report.txt"
was an <a> without href, so Tab skipped it. It is an action, so it becomes
a <button type="button"> with the same look, underline on hover included.
The type keeps it from sending the feedback form.

A new visual test reaches both pages through a mocked server error and
checks 0 px of difference and the download with Enter.

AI-assisted-by: claude-opus-5-5
This commit is contained in:
Eva Marco 2026-11-05 11:30:39 +01:00 committed by GitHub
parent 657ce8b054
commit 8ab9624f0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 72 additions and 2 deletions

View File

@ -947,3 +947,54 @@ test.describe("Context notification", () => {
await expect(warning).toHaveScreenshot(shot);
});
});
test.describe("Download report", () => {
test("Error page and feedback page links", async ({ page }) => {
const shot = { maxDiffPixelRatio: 0, threshold: 0 };
await DashboardPage.mockConfigFlags(page, [
"disable-onboarding",
"enable-user-feedback",
]);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await page.route("**/api/main/methods/get-projects?*", (route) =>
route.fulfill({
status: 500,
contentType: "application/transit+json",
body: '["^ ","~:type","~:server-error","~:code","~:unexpected"]',
}),
);
await page.goto(
`/?screen=dashboard-recent&team-id=${DashboardPage.anyTeamId}`,
);
const errorLink = page.getByText("Download report.txt", { exact: true });
await expect(errorLink).toBeVisible();
await page.mouse.move(10, 10);
await expect(errorLink).toHaveScreenshot(shot);
await errorLink.hover();
await expect(errorLink).toHaveScreenshot(shot);
const errorDownload = page.waitForEvent("download");
await errorLink.focus();
await page.keyboard.press("Enter");
expect((await errorDownload).suggestedFilename()).toBe("report.txt");
await page.getByRole("button", { name: "Contact support" }).click();
const feedbackLink = page.getByText("Download report.txt", {
exact: true,
});
await expect(page.getByText("Send")).toBeVisible();
await page.mouse.move(10, 700);
await expect(feedbackLink).toHaveScreenshot(shot);
await feedbackLink.hover();
await expect(feedbackLink).toHaveScreenshot(shot);
const feedbackDownload = page.waitForEvent("download");
await feedbackLink.focus();
await page.keyboard.press("Enter");
expect((await feedbackDownload).suggestedFilename()).toBe("report.txt");
// The button is in the feedback form: Enter must not send it.
await expect(page.getByText("Send", { exact: true })).toBeVisible();
});
});

View File

@ -131,7 +131,9 @@
:show-success? true}]
(when report
[:a {:class (stl/css :link :download-button) :on-click on-download}
[:button {:type "button"
:class (stl/css :link :download-button)
:on-click on-download}
(tr "labels.download" "report.txt")])]
[:> fm/submit-button*

View File

@ -81,5 +81,11 @@
display: inline-block;
min-inline-size: $sz-160;
text-align: center;
background: none;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
}

View File

@ -429,7 +429,10 @@
[:p {:class (stl/css :desc-text)} (tr "labels.internal-error.desc-message-second")]]
(when (some? report)
[:a {:class (stl/css :download-link) :on-click on-download} (tr "labels.download" "report.txt")])
[:button {:type "button"
:class (stl/css :download-link)
:on-click on-download}
(tr "labels.download" "report.txt")])
[:div {:class (stl/css :buttons-container)}
[:> button* {:variant "secondary"

View File

@ -208,6 +208,14 @@
color: var(--color-foreground-primary);
text-transform: lowercase;
background: none;
border: none;
padding: 0;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
.sign-info {