Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
35a42cc
feat(app-automate): run App Accessibility checks on an Espresso or XC…
Aaryan430 Oct 21, 2026
614cd66
feat(accessibility): read App Automate build and session links
Aaryan430 Oct 21, 2026
460cf61
test(accessibility): keep the server entry out of the registration test
Aaryan430 Oct 21, 2026
7918d61
feat(accessibility): list App Accessibility builds and reports, and r…
Aaryan430 Oct 21, 2026
2d61016
fix(accessibility): an empty listing is not an error
Aaryan430 Oct 21, 2026
f2ce96b
fix(accessibility): keep the fetch prompt within the budget with appR…
Aaryan430 Oct 22, 2026
dbe088e
test(accessibility): the app source is named by buildId's description…
Aaryan430 Oct 22, 2026
3ab8a78
Merge branch 'feat/a11y-app-builds' into feat/a11y-app-native-and-links
Aaryan430 Oct 22, 2026
82a2491
Merge branch 'feat/a11y-app-native-and-links' into feat/a11y-app-list…
Aaryan430 Oct 22, 2026
da2cb2d
feat(server): say which product checks what for accessibility
Aaryan430 Oct 22, 2026
c082935
feat(accessibility): app build summary and comparison, exact paging, …
Aaryan430 Oct 22, 2026
30bb2f7
fix(accessibility): match look-alike app issues one for one when comp…
Aaryan430 Oct 22, 2026
9f07edd
fix(accessibility): page a comparison's new and resolved issues with …
Aaryan430 Oct 22, 2026
22efa2f
Merge branch 'feat/a11y-app-builds' into feat/a11y-app-native-and-links
Aaryan430 Oct 23, 2026
d81bbec
fix(accessibility): address review on #505: native suites' setup, Tes…
Aaryan430 Oct 23, 2026
67058d5
Merge branch 'feat/a11y-app-native-and-links' into feat/a11y-app-list…
Aaryan430 Oct 23, 2026
ed45f13
fix(accessibility): address review on #506: report id shape, empty li…
Aaryan430 Oct 23, 2026
cfed6dc
Merge branch 'feat/a11y-app-list-and-manual' into feat/a11y-app-compa…
Aaryan430 Oct 23, 2026
1a3f22f
fix(accessibility): address review on #511: build order, untrusted na…
Aaryan430 Oct 23, 2026
41bf5a5
docs(accessibility): keep the no-scans note's comment on its function
Aaryan430 Oct 23, 2026
48e65c6
Merge branch 'feat/a11y-app-native-and-links' into feat/a11y-app-list…
Aaryan430 Oct 23, 2026
e1b0a29
Merge branch 'feat/a11y-app-list-and-manual' into feat/a11y-app-compa…
Aaryan430 Oct 23, 2026
9f0ebf5
Merge branch 'feat/a11y-app-builds' into feat/a11y-app-native-and-links
Aaryan430 Oct 23, 2026
3664751
Merge branch 'feat/a11y-app-native-and-links' into feat/a11y-app-list…
Aaryan430 Oct 23, 2026
7cfcc79
Merge branch 'feat/a11y-app-list-and-manual' into feat/a11y-app-compa…
Aaryan430 Oct 23, 2026
d467ee1
Merge branch 'feat/a11y-app-builds' into feat/a11y-app-native-and-links
Aaryan430 Oct 23, 2026
8dbba03
Merge branch 'feat/a11y-app-native-and-links' into feat/a11y-app-list…
Aaryan430 Oct 23, 2026
dcd94b5
Merge branch 'feat/a11y-app-list-and-manual' into feat/a11y-app-compa…
Aaryan430 Oct 23, 2026
908ec85
Merge branch 'feat/a11y-app-builds' into feat/a11y-app-native-and-links
Aaryan430 Oct 23, 2026
0d8fb75
Merge branch 'feat/a11y-app-native-and-links' into feat/a11y-app-list…
Aaryan430 Oct 23, 2026
dbcdef5
Merge branch 'feat/a11y-app-list-and-manual' into feat/a11y-app-compa…
Aaryan430 Oct 23, 2026
bc0c927
Merge remote-tracking branch 'origin/feat/a11y-app-builds' into feat/…
Aaryan430 Oct 23, 2026
d5bb6a1
fix(accessibility): address review on #505: say what a native suite n…
Aaryan430 Oct 23, 2026
b02a50c
Merge remote-tracking branch 'origin/feat/a11y-app-native-and-links' …
Aaryan430 Oct 23, 2026
f69c6cb
fix(accessibility): address review on #506: report a failed project s…
Aaryan430 Oct 23, 2026
fab8167
Merge remote-tracking branch 'origin/feat/a11y-app-list-and-manual' i…
Aaryan430 Oct 23, 2026
32e09da
fix(accessibility): address review on #511: failed build pages, nativ…
Aaryan430 Oct 23, 2026
80ffdaa
feat(accessibility): offer to write tests for an app that has none, i…
Aaryan430 Oct 23, 2026
c06de5e
Merge remote-tracking branch 'origin/feat/a11y-app-builds' into feat/…
Aaryan430 Oct 23, 2026
3693e2f
Merge remote-tracking branch 'origin/feat/a11y-app-native-and-links' …
Aaryan430 Oct 23, 2026
255721e
feat(accessibility): read a manual report as the dashboard does, when…
Aaryan430 Oct 23, 2026
d4e0455
Merge remote-tracking branch 'origin/feat/a11y-app-list-and-manual' i…
Aaryan430 Oct 23, 2026
0b31e43
Merge remote-tracking branch 'origin/feat/a11y-app-builds' into feat/…
Aaryan430 Oct 23, 2026
184acf7
Merge remote-tracking branch 'origin/feat/a11y-app-native-and-links' …
Aaryan430 Oct 23, 2026
bdbdcd6
Merge remote-tracking branch 'origin/feat/a11y-app-list-and-manual' i…
Aaryan430 Oct 23, 2026
6e01411
fix(accessibility): don't count issues on screens a later build didn'…
Aaryan430 Oct 23, 2026
c15a75a
refactor(accessibility): list App Accessibility work through capabili…
Aaryan430 Oct 23, 2026
9784c83
test(accessibility): answer the project-search cases by URL, so the l…
Aaryan430 Oct 23, 2026
ce06453
Merge remote-tracking branch 'origin/feat/a11y-app-list-and-manual' i…
Aaryan430 Oct 23, 2026
b4c3db6
Merge remote-tracking branch 'origin/feat/a11y-app-list-and-manual' i…
Aaryan430 Oct 23, 2026
11a7ead
Merge remote-tracking branch 'origin/feat/a11y-app-builds' into feat/…
Aaryan430 Oct 23, 2026
eec270c
fix: address review on #505: fail link lookups on their status, say w…
Aaryan430 Oct 23, 2026
bfc7382
Merge remote-tracking branch 'origin/feat/a11y-app-native-and-links' …
Aaryan430 Oct 23, 2026
b479db6
fix: address review on #506: read a manual report whole or say what i…
Aaryan430 Oct 23, 2026
e4e3c74
Merge remote-tracking branch 'origin/feat/a11y-app-list-and-manual' i…
Aaryan430 Oct 23, 2026
7c3853c
fix: address review on #511: drop the server-wide routing text, tight…
Aaryan430 Oct 23, 2026
152611a
Merge pull request #511 from browserstack/feat/a11y-app-compare-and-r…
Aaryan430 Oct 23, 2026
b7a0a32
Merge pull request #506 from browserstack/feat/a11y-app-list-and-manual
Aaryan430 Oct 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/TOOLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ searchCapability → describeCapability → invokeCapability
| `takeAppScreenshot` | Launch the app on a specified device and capture a quick verification screenshot to confirm your app has launched. |
| `listTestIds` | List the tests in a BrowserStack build (Automate or App Automate) with each test's `status` and `session_id`, optionally filtered by status (passed/failed/pending/skipped). The `session_id` feeds `getFailureLogs` and `fetchAutomationScreenshots` directly. |
| `setupBrowserStackAppAutomateTests` | Set up BrowserStack App Automate SDK integration for Appium-based mobile app testing. With `accessibility: true`, the run also performs App Accessibility (WCAG) checks on the screens the tests interact with, and the output says what makes a scan happen and how to read the issues. |
| `runAppTestsOnBrowserStack` | Run pre-built native mobile test suites (Espresso/XCUITest) by direct upload of compiled .apk/.ipa test files. |
| `runAppTestsOnBrowserStack` | Run pre-built native mobile test suites (Espresso/XCUITest) by direct upload of compiled .apk/.ipa test files. With `accessibility: true`, the build also performs App Accessibility (WCAG) checks, and the answer says how to read the issues once it finishes. The checks need a suite built with BrowserStack's instrumentation: for Espresso the Gradle plugin and Java SDK, for XCUITest the Swift SDK, embedded and initialized. |

</details>

Expand Down
49 changes: 47 additions & 2 deletions src/tools/accessibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,12 +407,57 @@ function appBuildDetails(report: AppBuildReport): string {
)}`;
}

/**
* Why a native (Espresso, XCUITest) build recorded no scans. The build request's flag only
* turns the checks on: the suite itself must carry BrowserStack's instrumentation.
*/
const NATIVE_NO_SCAN_CAUSES: Record<"espresso" | "xcuitest", string[]> = {
espresso: [
"The test suite was not built with BrowserStack's Gradle plugin and SDK: add classpath 'com.browserstack:gradle-tool:latest.release' to the root build.gradle, id 'com.browserstack.gradle-tool' to the app's plugins, and androidTestImplementation 'com.browserstack:browserstack-java-sdk:latest.release' (1.31.0 or newer), then rebuild the test APK.",
"Test Observability was off for the build: Espresso's App Accessibility report needs `testObservability: true` in the build request alongside `accessibility: true` (runAppTestsOnBrowserStack sends both).",
"The app cannot reach BrowserStack from the device: it needs internet permission, and cleartext traffic allowed (usesCleartextTraffic) if the network calls for it.",
"No tests ran: check the build's sessions and test results on App Automate.",
],
xcuitest: [
"The UI test target does not embed and initialize the BrowserStack Swift SDK: add BrowserstackSwiftSDK.xcframework (or the Swift package) to the test target, and set NSPrincipalClass to BrowserstackSwiftSDK.BrowserstackSwiftSDK in its Info.plist or call BrowserstackSwiftSDK.init() in the test class's init().",
"The device runs iOS older than 14, or the suite was built with Swift older than 5.4.",
"No tests ran: check the build's sessions and test results on App Automate.",
],
};

/** Which native framework ran a build, when its details name one. */
function nativeFramework(
framework: string | undefined,
): "espresso" | "xcuitest" | undefined {
if (!framework) return undefined;
if (/espresso/i.test(framework)) return "espresso";
if (/xcui|xctest/i.test(framework)) return "xcuitest";
return undefined;
}

/**
* Why a finished app build has no scans. Each cause here produced a run that finished
* green with nothing scanned and no warning; they are listed most likely first, with what
* the build's own details say ahead of the rest.
* the build's own details say ahead of the rest. A native build gets the native causes.
*/
function noScansNote(report: AppBuildReport): string[] {
const native = nativeFramework(report.framework);
if (native) {
const causes = [
...(report.fup_capped
? [
"This build hit the fair-usage cap (fup in the build details), which drops tests or scans.",
]
: []),
...NATIVE_NO_SCAN_CAUSES[native],
];
return [
`No accessibility scans were recorded for the ${native === "espresso" ? "Espresso" : "XCUITest"} build below (status: ${report.status}), so there are no issues to list.`,
appBuildDetails(report),
"The usual causes for a native build, most likely first:",
...causes.map((cause, i) => `${i + 1}. ${cause}`),
];
}
const causes = [
...(report.fup_capped
? [
Expand Down Expand Up @@ -987,7 +1032,7 @@ export default function addAccessibilityTools(
.string()
.optional()
.describe(
"A Website Scanner, Automated Tests or App Accessibility report link from its dashboard.",
"A Website Scanner, Automated Tests or App Accessibility report link, or an App Automate build or session link.",
),
cursor: z
.number()
Expand Down
198 changes: 181 additions & 17 deletions src/tools/accessiblity-utils/app-report-fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { apiClient } from "../../lib/apiClient.js";
import { SessionType } from "../../lib/constants.js";
import { sessionsListUrl } from "../automate-utils/list-session-ids.js";
import { getAutomationBaseUrl } from "../rca-agent-utils/constants.js";
import { pageOf, sortBySeverity } from "./paginate.js";

/**
Expand All @@ -18,6 +21,12 @@ export const MAX_SNAPSHOT_PAGES = 20;
export const MAX_APP_BUILD_LIST_PAGES = 3;
const BUILD_LIST_PAGE_SIZE = 30;

/** A native build is found by name in the account's App Automate build list, newest first. */
const APP_AUTOMATE_BUILDS_URL =
"https://api-cloud.browserstack.com/app-automate/builds.json";
const APP_AUTOMATE_BUILDS_PAGE_SIZE = 100;
export const MAX_APP_AUTOMATE_BUILD_PAGES = 3;

export interface AppBuildMeta {
/** The build's number within its project, which the dashboard link uses. */
build_id: number;
Expand All @@ -29,6 +38,8 @@ export interface AppBuildMeta {
status: string;
issue_summary?: { total?: number; scan_count?: number };
fup?: { capped?: boolean; tests_partial?: boolean; tests_dropped?: boolean };
/** What ran the tests: an SDK framework (pytest, …) or a native one (Espresso-Junit4, …). */
session_data?: { framework?: { name?: string } };
}

interface AppRuleIssue {
Expand Down Expand Up @@ -89,6 +100,8 @@ export interface AppBuildReport {
dashboard_link?: string;
/** Whether the fair-usage cap dropped tests or scans from this build. */
fup_capped: boolean;
/** The test framework the build ran under, when the build's details name it. */
framework?: string;
/** How many screens were read, when the snapshot page limit stopped short of them all. */
screens_read?: number;
}
Expand Down Expand Up @@ -191,6 +204,19 @@ export function flattenAppSnapshots(
return sortBySeverity(rows);
}

const APP_AUTOMATE_HOST = "app-automate.browserstack.com";

/** The App Automate build and, when the link names one, the session (both 40-hex ids). */
function appAutomateIds(
url: URL,
): { buildId?: string; sessionId?: string } | undefined {
if (url.hostname !== APP_AUTOMATE_HOST) return undefined;
return {
buildId: url.pathname.match(/\/builds\/([0-9a-f]{40})(?:\/|$)/i)?.[1],
sessionId: url.pathname.match(/\/sessions\/([0-9a-f]{40})(?:\/|$)/i)?.[1],
};
}

/** The build an App Accessibility dashboard link names: project id, build name and number. */
function buildOfDashboardLink(
url: URL,
Expand Down Expand Up @@ -230,37 +256,172 @@ export class AppAccessibilityApi {
if (url.hostname !== new URL(APP_DASHBOARD).hostname) return undefined;
const build = buildOfDashboardLink(url);
if (!build) return undefined;
const buildId = await this.findBuild(
build.projectId,
undefined,
(row) => row.build_number === build.number,
);
if (buildId) return buildId;
throw new Error(
`No build ${build.number} of "${build.name}" is among the project's newest builds (${MAX_APP_BUILD_LIST_PAGES} pages). Pass the build id instead, as buildId: the BrowserStack SDK prints it as "testhub id" at the end of the run`,
);
}

/**
* The accessibility build of an App Automate build or session link. An SDK (Appium)
* session's test code carries the TestHub build id its results went to, which is the
* App Accessibility build when accessibility was on; a build link goes through any one
* of its sessions. A native (Espresso, XCUITest) build has no SDK session, and is found
* by name instead.
*/
async buildIdFromAppAutomateLink(url: URL): Promise<string | undefined> {
const ids = appAutomateIds(url);
if (!ids?.buildId && !ids?.sessionId) return undefined;
if (ids.sessionId) return this.buildOfSdkSession(ids.sessionId);
const session = await this.anySdkSession(ids.buildId!);
return session
? this.buildOfSdkSession(session)
: this.buildOfNativeBuild(ids.buildId!);
}

private async buildOfSdkSession(sessionId: string): Promise<string> {
const resp = await apiClient.get({
url: `${getAutomationBaseUrl()}/ext/v1/sessions/${encodeURIComponent(sessionId)}/testCode`,
headers: this.headers(),
raise_error: false,
});
// A session with no test code is a 404; any other failure is the request's, not the run's.
if (!resp.ok && resp.status !== 404) {
throw new Error(
`Could not read App Automate session ${sessionId}: HTTP ${resp.status}`,
);
}
const buildId = resp.ok ? resp.data?.build_uuid : undefined;
if (!buildId) {
throw new Error(
`App Automate session ${sessionId} has no accessibility report: accessibility was not on for this run, or this is not an SDK (Appium) session. Run it again with accessibility on (browserstack.yml \`accessibility: true\`)`,
);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
return buildId;
}

/** The first SDK session of an App Automate build; a native build lists none. */
private async anySdkSession(buildId: string): Promise<string | undefined> {
const resp = await apiClient.get({
url: `${sessionsListUrl(SessionType.AppAutomate, buildId)}?limit=1`,
headers: this.headers(),
raise_error: false,
});
return resp.ok ? resp.data?.[0]?.automation_session?.hashed_id : undefined;
}

/**
* A native build's details name its project; the App Automate build list names the build
* ("Build #N: app vX"); App Accessibility lists the run under the same project and name.
*/
private async buildOfNativeBuild(buildId: string): Promise<string> {
const project = await this.nativeBuildProject(buildId);
if (!project) {
throw new Error(
`No session found for App Automate build ${buildId}, and it is not an Espresso or XCUITest build with a project, so it cannot be mapped to its accessibility report. Pass the build id the BrowserStack SDK printed as "testhub id" as buildId, or the build's App Accessibility dashboard link as reportUrl`,
);
}
const name = await this.appAutomateBuildName(buildId);
if (!name) {
throw new Error(
`App Automate build ${buildId} is not among the account's newest builds (${MAX_APP_AUTOMATE_BUILD_PAGES} pages of ${APP_AUTOMATE_BUILDS_PAGE_SIZE}), so its accessibility report cannot be found by name. Pass the build's App Accessibility dashboard link as reportUrl instead`,
);
}
const projectId = await this.projectId(project);
const found =
projectId !== undefined &&
(await this.findBuild(projectId, name, (row) => row.name === name));
if (!found) {
throw new Error(
`No App Accessibility build named "${name}" in project "${project}": accessibility was not on for this run. Run it again with \`accessibility: true\` in the Espresso/XCUITest build request`,
);
}
return found;
}

/** The project a native build was run under, from whichever framework API knows it. */
private async nativeBuildProject(
buildId: string,
): Promise<string | undefined> {
for (const framework of ["espresso", "xcuitest"]) {
const resp = await apiClient.get({
url: `https://api-cloud.browserstack.com/app-automate/${framework}/v2/builds/${encodeURIComponent(buildId)}`,
headers: this.headers(),
raise_error: false,
});
if (resp.ok) return resp.data?.input_capabilities?.project;
}
return undefined;
}

/** The build's name in the account's App Automate build list, walked newest first. */
private async appAutomateBuildName(
buildId: string,
): Promise<string | undefined> {
for (let page = 0; page < MAX_APP_AUTOMATE_BUILD_PAGES; page++) {
const resp = await apiClient.get({
url: `${APP_AUTOMATE_BUILDS_URL}?limit=${APP_AUTOMATE_BUILDS_PAGE_SIZE}&offset=${page * APP_AUTOMATE_BUILDS_PAGE_SIZE}`,
headers: this.headers(),
raise_error: false,
});
const rows: {
automation_build?: { hashed_id?: string; name?: string };
}[] = resp.ok && Array.isArray(resp.data) ? resp.data : [];
const row = rows.find((r) => r.automation_build?.hashed_id === buildId);
if (row) return row.automation_build?.name;
if (rows.length < APP_AUTOMATE_BUILDS_PAGE_SIZE) break;
}
return undefined;
}

/** The App Accessibility project of that exact name, if any. */
private async projectId(name: string): Promise<number | undefined> {
const resp = await apiClient.get({
url: `${APP_API_BASE}/project?name=${encodeURIComponent(name)}`,
headers: this.headers(),
raise_error: false,
});
return resp.data?.projects?.find(
(p: { id?: number; name?: string }) => p.name === name,
)?.id;
}

/**
* The id of the project's build that `pick` accepts, newest first, within the first
* pages; `buildName` narrows the list to builds named like it, when the name is known
* to be the build's own.
*/
private async findBuild(
projectId: string | number,
buildName: string | undefined,
pick: (row: { name?: string; build_number?: number }) => boolean,
): Promise<string | undefined> {
const q = encodeURIComponent;
const named = buildName ? `build_name=${q(buildName)}&` : "";
for (let page = 1; page <= MAX_APP_BUILD_LIST_PAGES; page++) {
const resp = await apiClient.get({
url: `${APP_API_BASE}/project/${q(build.projectId)}/build?page_size=${BUILD_LIST_PAGE_SIZE}&page=${page}`,
url: `${APP_API_BASE}/project/${q(String(projectId))}/build?${named}page_size=${BUILD_LIST_PAGE_SIZE}&page=${page}`,
headers: this.headers(),
raise_error: false,
});
const row = resp.data?.builds?.find(
(b: { build_number?: number }) => b.build_number === build.number,
);
const row = resp.data?.builds?.find(pick);
if (row?.th_build_id) return row.th_build_id;
if (!resp.ok || !resp.data?.info?.next) break;
}
throw new Error(
`No build ${build.number} of "${build.name}" is among the project's newest builds (${MAX_APP_BUILD_LIST_PAGES} pages). Pass the build id instead, as buildId: the BrowserStack SDK prints it as "testhub id" at the end of the run`,
);
return undefined;
}

/** The build's page on the App Accessibility dashboard, when its project can be found. */
private async dashboardLink(meta: AppBuildMeta): Promise<string | undefined> {
try {
const resp = await apiClient.get({
url: `${APP_API_BASE}/project?name=${encodeURIComponent(meta.project_name)}`,
headers: this.headers(),
raise_error: false,
});
const project = resp.data?.projects?.find(
(p: { id?: number; name?: string }) => p.name === meta.project_name,
);
if (!project?.id) return undefined;
return `${APP_DASHBOARD}/automated-tests/projects/${project.id}/builds/${meta.normalised_name}/${meta.build_id}`;
const projectId = await this.projectId(meta.project_name);
if (projectId === undefined) return undefined;
return `${APP_DASHBOARD}/automated-tests/projects/${projectId}/builds/${meta.normalised_name}/${meta.build_id}`;
} catch {
return undefined;
}
Expand Down Expand Up @@ -317,6 +478,9 @@ export class AppAccessibilityApi {
build_name: meta.name,
dashboard_link: await this.dashboardLink(meta),
fup_capped: Boolean(meta.fup?.capped || meta.fup?.tests_dropped),
...(meta.session_data?.framework?.name
? { framework: meta.session_data.framework.name }
: {}),
...(more ? { screens_read: details.length } : {}),
};
}
Expand Down
10 changes: 7 additions & 3 deletions src/tools/accessiblity-utils/report-fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ export class AccessibilityReportFetcher {
* list can map to ids: each scan there carries its latest run's link. A link to an older
* run therefore matches no scan, and is reported as such rather than guessed at. An App
* Accessibility link names its build by project, name and number, which the project's
* build list maps to the id.
* build list maps to the id. An App Automate build or session link maps to the
* accessibility build through a session's test code.
*/
async sourceFromReportUrl(reportUrl: string): Promise<IssueSource> {
let url: URL | undefined;
Expand All @@ -147,7 +148,10 @@ export class AccessibilityReportFetcher {
} catch {
url = undefined;
}
const appBuildId = url && (await this.app.buildIdFromDashboardLink(url));
const appBuildId =
url &&
((await this.app.buildIdFromDashboardLink(url)) ??
(await this.app.buildIdFromAppAutomateLink(url)));
if (appBuildId) return { kind: "appBuild", buildId: appBuildId };
const thBuildId = url?.searchParams.get("thBuildId");
if (url && thBuildId && url.pathname.includes("/automated-tests/")) {
Expand All @@ -160,7 +164,7 @@ export class AccessibilityReportFetcher {
return this.findScanRun(thBuildId, decodeURIComponent(scanName));
}
throw new Error(
"This link does not name a report the API can look up. Pass an Automated Tests or Website Scanner report link from the Accessibility dashboard (both carry thBuildId), an App Accessibility build link (automated-tests/projects/…/builds/…), or the ids: scanId with scanRunId, buildId, or workflowReportId",
"This link does not name a report the API can look up. Pass an Automated Tests or Website Scanner report link from the Accessibility dashboard (both carry thBuildId), an App Accessibility build link (automated-tests/projects/…/builds/…), an App Automate build or session link, or the ids: scanId with scanRunId, buildId, or workflowReportId",
);
}

Expand Down
Loading