@browserstack/mcp-server 2.2.6-beta.2 → 2.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server-factory.js +1 -6
- package/dist/tools/accessibility.js +69 -680
- package/dist/tools/accessiblity-utils/accessibility-rag.d.ts +12 -0
- package/dist/tools/accessiblity-utils/accessibility-rag.js +59 -0
- package/dist/tools/accessiblity-utils/report-fetcher.d.ts +7 -116
- package/dist/tools/accessiblity-utils/report-fetcher.js +29 -268
- package/dist/tools/accessiblity-utils/report-parser.d.ts +1 -41
- package/dist/tools/accessiblity-utils/report-parser.js +43 -142
- package/dist/tools/accessiblity-utils/scanner.d.ts +1 -15
- package/dist/tools/accessiblity-utils/scanner.js +4 -3
- package/dist/tools/appautomate-utils/appium-sdk/config-generator.d.ts +0 -2
- package/dist/tools/appautomate-utils/appium-sdk/config-generator.js +5 -15
- package/dist/tools/appautomate-utils/appium-sdk/constants.d.ts +1 -2
- package/dist/tools/appautomate-utils/appium-sdk/constants.js +1 -6
- package/dist/tools/appautomate-utils/appium-sdk/handler.js +1 -12
- package/dist/tools/appautomate-utils/appium-sdk/instructions.d.ts +1 -3
- package/dist/tools/appautomate-utils/appium-sdk/instructions.js +2 -2
- package/dist/tools/appautomate-utils/appium-sdk/languages/nodejs.d.ts +1 -3
- package/dist/tools/appautomate-utils/appium-sdk/languages/nodejs.js +6 -19
- package/dist/tools/appautomate-utils/native-execution/appautomate.d.ts +2 -7
- package/dist/tools/appautomate-utils/native-execution/appautomate.js +12 -15
- package/dist/tools/appautomate-utils/native-execution/constants.d.ts +1 -2
- package/dist/tools/appautomate-utils/native-execution/constants.js +1 -6
- package/dist/tools/appautomate.js +16 -29
- package/dist/tools/testmanagement-utils/create-testrun.d.ts +1 -1
- package/dist/tools/testmanagement-utils/update-testrun.d.ts +1 -1
- package/dist/tools/tool-handoff.d.ts +2 -3
- package/dist/tools/tool-handoff.js +2 -3
- package/package.json +1 -1
- package/dist/lib/accessibility-routing.d.ts +0 -6
- package/dist/lib/accessibility-routing.js +0 -6
- package/dist/tools/accessiblity-utils/app-report-fetcher.d.ts +0 -412
- package/dist/tools/accessiblity-utils/app-report-fetcher.js +0 -926
- package/dist/tools/accessiblity-utils/paginate.d.ts +0 -27
- package/dist/tools/accessiblity-utils/paginate.js +0 -50
- package/dist/tools/appautomate-utils/appium-sdk/languages/accessibility.d.ts +0 -4
- package/dist/tools/appautomate-utils/appium-sdk/languages/accessibility.js +0 -47
|
@@ -11,32 +11,12 @@ type SimplifiedAccessibilityIssue = {
|
|
|
11
11
|
source_test?: string;
|
|
12
12
|
/** Grouped reports only: how many duplicate rows point at this issue. */
|
|
13
13
|
duplicate_count?: number;
|
|
14
|
-
/** Read for one page: how many times the issue occurs on that page. */
|
|
15
|
-
occurrences_on_page?: number;
|
|
16
|
-
/** Read for one page: the page the issue was first found on, when another one. */
|
|
17
|
-
first_found_on?: string;
|
|
18
|
-
};
|
|
19
|
-
/** One page of a report, counted as the dashboard's page view counts it. */
|
|
20
|
-
type PageSummary = {
|
|
21
|
-
url: string;
|
|
22
|
-
/** Every occurrence on the page, repeats of issues first found elsewhere included. */
|
|
23
|
-
occurrences: number;
|
|
24
|
-
/** The issues those occurrences are of. */
|
|
25
|
-
distinct_issues: number;
|
|
26
|
-
/** Of those, the ones first found on this page. */
|
|
27
|
-
first_found_here: number;
|
|
28
|
-
/** Occurrences by severity. */
|
|
29
|
-
by_severity: Record<string, number>;
|
|
30
|
-
/** When nothing is on the page: report pages whose address looks like it. */
|
|
31
|
-
similar_pages?: string[];
|
|
32
14
|
};
|
|
33
15
|
type PaginationOptions = {
|
|
34
16
|
/** How many JSON-chars max per “page” (default 10000) */
|
|
35
17
|
maxCharacterLength?: number;
|
|
36
18
|
/** Character offset to start from (default 0) */
|
|
37
19
|
nextPage?: number;
|
|
38
|
-
/** Only the issues on this page. */
|
|
39
|
-
pageUrl?: string;
|
|
40
20
|
};
|
|
41
21
|
type PaginatedResult = {
|
|
42
22
|
records: SimplifiedAccessibilityIssue[];
|
|
@@ -45,32 +25,12 @@ type PaginatedResult = {
|
|
|
45
25
|
/** Main issues only when the report is grouped, so it matches the run's issue count. */
|
|
46
26
|
total_issues: number;
|
|
47
27
|
next_page: number | null;
|
|
48
|
-
/** How many issues the pages after this one hold. */
|
|
49
|
-
remaining: number;
|
|
50
28
|
/** Duplicate rows left out of `records`; null when the report is not grouped. */
|
|
51
29
|
duplicate_issues: number | null;
|
|
52
30
|
/** Of those, the ones whose main issue could not be identified (counted only here). */
|
|
53
31
|
unlinked_duplicates: number;
|
|
54
32
|
/** Issues hidden in the dashboard and left out; null when the export has no Issue status. */
|
|
55
33
|
hidden_issues: number | null;
|
|
56
|
-
/** Set when the report was read for one page. */
|
|
57
|
-
page?: PageSummary;
|
|
58
|
-
};
|
|
59
|
-
/** A main issue as two runs are matched on: the rule, the element and the page. */
|
|
60
|
-
export type RunIssue = {
|
|
61
|
-
key: string;
|
|
62
|
-
issue_type: string;
|
|
63
|
-
severity: string;
|
|
64
|
-
component: string;
|
|
65
|
-
affected_page: string;
|
|
66
|
-
};
|
|
67
|
-
/** A report's main issues that the dashboard counts (not hidden), keyed for comparing runs. */
|
|
68
|
-
export declare function loadMainIssues(reportLink: string): Promise<RunIssue[]>;
|
|
69
|
-
/** Which issues of `current` are new since `base`, which of `base` are gone, and how many remain. */
|
|
70
|
-
export declare function compareIssues(base: RunIssue[], current: RunIssue[]): {
|
|
71
|
-
added: RunIssue[];
|
|
72
|
-
resolved: RunIssue[];
|
|
73
|
-
retained: number;
|
|
74
34
|
};
|
|
75
|
-
export declare function parseAccessibilityReportFromCSV(reportLink: string, { maxCharacterLength, nextPage
|
|
35
|
+
export declare function parseAccessibilityReportFromCSV(reportLink: string, { maxCharacterLength, nextPage }?: PaginationOptions): Promise<PaginatedResult>;
|
|
76
36
|
export {};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { apiClient } from "../../lib/apiClient.js";
|
|
2
2
|
import { parse } from "csv-parse/sync";
|
|
3
3
|
import logger from "../../logger.js";
|
|
4
|
-
import { PAGE_CHARACTERS, pageOf, sortBySeverity } from "./paginate.js";
|
|
5
4
|
const SOURCE_TEST_COLUMN = "Source test";
|
|
6
5
|
const DUPLICATE_COLUMN = "Duplicate Issue of";
|
|
7
6
|
const ISSUE_LINK_COLUMN = "Issue link";
|
|
@@ -15,29 +14,8 @@ function isMainIssue(row) {
|
|
|
15
14
|
const value = (row[DUPLICATE_COLUMN] ?? "").trim();
|
|
16
15
|
return value === "" || value.toUpperCase() === "NA";
|
|
17
16
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const u = new URL(url.trim());
|
|
22
|
-
return `${u.protocol}//${u.host.toLowerCase()}${u.pathname.replace(/\/+$/, "")}${u.search}`;
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return url.trim().replace(/\/+$/, "");
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function recordOfRow(row, hasSourceTest) {
|
|
29
|
-
return {
|
|
30
|
-
issue_type: row["Issue type"],
|
|
31
|
-
component: row["Component"],
|
|
32
|
-
issue_description: row["Issue description"],
|
|
33
|
-
HTML_snippet: row["HTML snippet"],
|
|
34
|
-
how_to_fix: row["How to fix this issue"],
|
|
35
|
-
severity: (row["Severity"] || "unknown").trim(),
|
|
36
|
-
affected_page: row["Affected page"],
|
|
37
|
-
...(hasSourceTest ? { source_test: row[SOURCE_TEST_COLUMN] } : {}),
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
async function loadReport(reportLink) {
|
|
17
|
+
export async function parseAccessibilityReportFromCSV(reportLink, { maxCharacterLength = 5000, nextPage = 0 } = {}) {
|
|
18
|
+
// 1) Download & parse
|
|
41
19
|
const res = await apiClient.get({ url: reportLink });
|
|
42
20
|
if (!res.ok)
|
|
43
21
|
throw new Error(`Failed to download report: ${res.statusText}`);
|
|
@@ -58,119 +36,6 @@ async function loadReport(reportLink) {
|
|
|
58
36
|
logger.error(`[AccessibilityReport] CSV parse failed (${text.length} chars): ${err instanceof Error ? err.message : String(err)}`);
|
|
59
37
|
throw err;
|
|
60
38
|
}
|
|
61
|
-
return { parsed, columns };
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* One page of a report: every occurrence on it, as the dashboard's page view counts, with
|
|
65
|
-
* each issue listed once. A grouped report records a repeat under the page it occurs on but
|
|
66
|
-
* points it at its main issue, which may be on another page; those count here too.
|
|
67
|
-
*/
|
|
68
|
-
function pageIssues(parsed, columns, pageUrl) {
|
|
69
|
-
const grouped = columns.includes(DUPLICATE_COLUMN);
|
|
70
|
-
const hasSourceTest = columns.includes(SOURCE_TEST_COLUMN);
|
|
71
|
-
const target = samePage(pageUrl);
|
|
72
|
-
const visible = parsed.filter((row) => !isHidden(row));
|
|
73
|
-
const onPage = visible.filter((row) => samePage(row["Affected page"] ?? "") === target);
|
|
74
|
-
const mainByLink = new Map();
|
|
75
|
-
for (const row of visible) {
|
|
76
|
-
if (!grouped || isMainIssue(row)) {
|
|
77
|
-
mainByLink.set((row[ISSUE_LINK_COLUMN] ?? "").trim(), row);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
const issues = new Map();
|
|
81
|
-
const bySeverity = {};
|
|
82
|
-
onPage.forEach((row, i) => {
|
|
83
|
-
const severity = (row["Severity"] || "unknown").trim();
|
|
84
|
-
bySeverity[severity] = (bySeverity[severity] ?? 0) + 1;
|
|
85
|
-
const mainLink = grouped && !isMainIssue(row)
|
|
86
|
-
? (row[DUPLICATE_COLUMN] ?? "").trim()
|
|
87
|
-
: (row[ISSUE_LINK_COLUMN] ?? "").trim();
|
|
88
|
-
const key = mainLink || `row-${i}`;
|
|
89
|
-
let issue = issues.get(key);
|
|
90
|
-
if (!issue) {
|
|
91
|
-
const main = mainByLink.get(mainLink) ?? row;
|
|
92
|
-
issue = {
|
|
93
|
-
...recordOfRow(main, hasSourceTest),
|
|
94
|
-
affected_page: row["Affected page"],
|
|
95
|
-
occurrences_on_page: 0,
|
|
96
|
-
};
|
|
97
|
-
if (samePage(main["Affected page"] ?? "") !== target) {
|
|
98
|
-
issue.first_found_on = main["Affected page"];
|
|
99
|
-
}
|
|
100
|
-
issues.set(key, issue);
|
|
101
|
-
}
|
|
102
|
-
issue.occurrences_on_page = (issue.occurrences_on_page ?? 0) + 1;
|
|
103
|
-
});
|
|
104
|
-
const all = [...issues.values()];
|
|
105
|
-
const page = {
|
|
106
|
-
url: pageUrl,
|
|
107
|
-
occurrences: onPage.length,
|
|
108
|
-
distinct_issues: all.length,
|
|
109
|
-
first_found_here: all.filter((issue) => !issue.first_found_on).length,
|
|
110
|
-
by_severity: bySeverity,
|
|
111
|
-
};
|
|
112
|
-
if (onPage.length === 0) {
|
|
113
|
-
const tail = new URL(target, "https://x").pathname.split("/").filter(Boolean).pop() ??
|
|
114
|
-
"";
|
|
115
|
-
const stem = tail.split("-").slice(0, 2).join("-");
|
|
116
|
-
// A short row may have no Affected page at all.
|
|
117
|
-
const pages = [
|
|
118
|
-
...new Set(visible
|
|
119
|
-
.map((row) => row["Affected page"])
|
|
120
|
-
.filter((p) => Boolean(p))),
|
|
121
|
-
];
|
|
122
|
-
page.similar_pages = pages
|
|
123
|
-
.filter((p) => stem && samePage(p).includes(stem))
|
|
124
|
-
.slice(0, 5);
|
|
125
|
-
}
|
|
126
|
-
return { all, page };
|
|
127
|
-
}
|
|
128
|
-
/** A report's main issues that the dashboard counts (not hidden), keyed for comparing runs. */
|
|
129
|
-
export async function loadMainIssues(reportLink) {
|
|
130
|
-
const { parsed, columns } = await loadReport(reportLink);
|
|
131
|
-
const grouped = columns.includes(DUPLICATE_COLUMN);
|
|
132
|
-
return parsed
|
|
133
|
-
.filter((row) => (!grouped || isMainIssue(row)) && !isHidden(row))
|
|
134
|
-
.map((row) => {
|
|
135
|
-
const element = (row["CSS selector"] || row["Component"] || "").trim();
|
|
136
|
-
return {
|
|
137
|
-
key: [
|
|
138
|
-
row["Issue type"],
|
|
139
|
-
element,
|
|
140
|
-
samePage(row["Affected page"] ?? ""),
|
|
141
|
-
].join("|"),
|
|
142
|
-
issue_type: row["Issue type"],
|
|
143
|
-
severity: (row["Severity"] || "unknown").trim(),
|
|
144
|
-
component: row["Component"],
|
|
145
|
-
affected_page: row["Affected page"],
|
|
146
|
-
};
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
/** Which issues of `current` are new since `base`, which of `base` are gone, and how many remain. */
|
|
150
|
-
export function compareIssues(base, current) {
|
|
151
|
-
const before = new Set(base.map((issue) => issue.key));
|
|
152
|
-
const after = new Set(current.map((issue) => issue.key));
|
|
153
|
-
return {
|
|
154
|
-
added: sortBySeverity(current.filter((issue) => !before.has(issue.key))),
|
|
155
|
-
resolved: sortBySeverity(base.filter((issue) => !after.has(issue.key))),
|
|
156
|
-
retained: current.filter((issue) => before.has(issue.key)).length,
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
export async function parseAccessibilityReportFromCSV(reportLink, { maxCharacterLength = PAGE_CHARACTERS, nextPage = 0, pageUrl, } = {}) {
|
|
160
|
-
// 1) Download & parse
|
|
161
|
-
const { parsed, columns } = await loadReport(reportLink);
|
|
162
|
-
if (pageUrl) {
|
|
163
|
-
const { all, page } = pageIssues(parsed, columns, pageUrl);
|
|
164
|
-
const slice = pageOf(sortBySeverity(all), { maxCharacterLength, nextPage });
|
|
165
|
-
return {
|
|
166
|
-
...slice,
|
|
167
|
-
total_issues: all.length,
|
|
168
|
-
duplicate_issues: null,
|
|
169
|
-
unlinked_duplicates: 0,
|
|
170
|
-
hidden_issues: null,
|
|
171
|
-
page,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
39
|
// 2) Leave out hidden issues and fold duplicates into their main issue, as the
|
|
175
40
|
// dashboard's count does.
|
|
176
41
|
//
|
|
@@ -190,7 +55,14 @@ export async function parseAccessibilityReportFromCSV(reportLink, { maxCharacter
|
|
|
190
55
|
const recordOf = new Map();
|
|
191
56
|
const all = visibleMainRows.map((row) => {
|
|
192
57
|
const record = {
|
|
193
|
-
|
|
58
|
+
issue_type: row["Issue type"],
|
|
59
|
+
component: row["Component"],
|
|
60
|
+
issue_description: row["Issue description"],
|
|
61
|
+
HTML_snippet: row["HTML snippet"],
|
|
62
|
+
how_to_fix: row["How to fix this issue"],
|
|
63
|
+
severity: (row["Severity"] || "unknown").trim(),
|
|
64
|
+
affected_page: row["Affected page"],
|
|
65
|
+
...(hasSourceTest ? { source_test: row[SOURCE_TEST_COLUMN] } : {}),
|
|
194
66
|
...(linkable ? { duplicate_count: 0 } : {}),
|
|
195
67
|
};
|
|
196
68
|
recordOf.set(row, record);
|
|
@@ -219,11 +91,40 @@ export async function parseAccessibilityReportFromCSV(reportLink, { maxCharacter
|
|
|
219
91
|
else
|
|
220
92
|
unlinkedDuplicates++;
|
|
221
93
|
}
|
|
222
|
-
|
|
223
|
-
|
|
94
|
+
const totalIssues = all.length;
|
|
95
|
+
// 3) Sort by severity
|
|
96
|
+
const order = {
|
|
97
|
+
critical: 0,
|
|
98
|
+
serious: 1,
|
|
99
|
+
moderate: 2,
|
|
100
|
+
minor: 3,
|
|
101
|
+
};
|
|
102
|
+
all.sort((a, b) => (order[a.severity] ?? 99) - (order[b.severity] ?? 99));
|
|
103
|
+
// 4) Walk to the starting offset
|
|
104
|
+
let charCursor = 0;
|
|
105
|
+
let idx = 0;
|
|
106
|
+
for (; idx < all.length; idx++) {
|
|
107
|
+
const len = JSON.stringify(all[idx]).length;
|
|
108
|
+
if (charCursor + len > nextPage)
|
|
109
|
+
break;
|
|
110
|
+
charCursor += len;
|
|
111
|
+
}
|
|
112
|
+
// 5) Collect up to maxCharacterLength
|
|
113
|
+
const page = [];
|
|
114
|
+
for (let i = idx; i < all.length; i++) {
|
|
115
|
+
const recStr = JSON.stringify(all[i]);
|
|
116
|
+
if (charCursor - nextPage + recStr.length > maxCharacterLength)
|
|
117
|
+
break;
|
|
118
|
+
page.push(all[i]);
|
|
119
|
+
charCursor += recStr.length;
|
|
120
|
+
}
|
|
121
|
+
const pageLength = page.length;
|
|
122
|
+
const hasMore = idx + pageLength < totalIssues;
|
|
224
123
|
return {
|
|
225
|
-
|
|
226
|
-
|
|
124
|
+
records: page,
|
|
125
|
+
next_page: hasMore ? charCursor : null,
|
|
126
|
+
page_length: pageLength,
|
|
127
|
+
total_issues: totalIssues,
|
|
227
128
|
duplicate_issues: grouped ? duplicates : null,
|
|
228
129
|
unlinked_duplicates: unlinkedDuplicates,
|
|
229
130
|
hidden_issues: columns.includes(STATUS_COLUMN)
|
|
@@ -13,27 +13,13 @@ export interface AccessibilityScanStatus {
|
|
|
13
13
|
};
|
|
14
14
|
errors?: string[];
|
|
15
15
|
}
|
|
16
|
-
/**
|
|
17
|
-
* What a Website Scanner scan checks for, as the create-scan API takes it under
|
|
18
|
-
* scanSettings. A setting left out takes the API's default.
|
|
19
|
-
*/
|
|
20
|
-
export interface ScanSettings {
|
|
21
|
-
/** The WCAG version and level, such as "wcag22aa", or "rgaa41" (API default "wcag21aa"). */
|
|
22
|
-
wcagVersion?: string;
|
|
23
|
-
/** Report best-practice issues too (API default false). */
|
|
24
|
-
bestPractices?: boolean;
|
|
25
|
-
/** Report issues that need a manual review (API default true). */
|
|
26
|
-
needsReview?: boolean;
|
|
27
|
-
/** Run the advanced rules (API default true). */
|
|
28
|
-
advancedRules?: boolean;
|
|
29
|
-
}
|
|
30
16
|
export declare class AccessibilityScanner {
|
|
31
17
|
private auth;
|
|
32
18
|
setAuth(auth: {
|
|
33
19
|
username: string;
|
|
34
20
|
password: string;
|
|
35
21
|
}): void;
|
|
36
|
-
startScan(name: string, urlList: string[], authConfigId?: number,
|
|
22
|
+
startScan(name: string, urlList: string[], authConfigId?: number, advancedRules?: boolean): Promise<AccessibilityScanResponse>;
|
|
37
23
|
pollStatus(scanId: string, scanRunId: string): Promise<AccessibilityScanStatus>;
|
|
38
24
|
waitUntilComplete(scanId: string, scanRunId: string, context: any): Promise<string>;
|
|
39
25
|
}
|
|
@@ -8,7 +8,7 @@ export class AccessibilityScanner {
|
|
|
8
8
|
setAuth(auth) {
|
|
9
9
|
this.auth = auth;
|
|
10
10
|
}
|
|
11
|
-
async startScan(name, urlList, authConfigId,
|
|
11
|
+
async startScan(name, urlList, authConfigId, advancedRules) {
|
|
12
12
|
if (!this.auth?.username || !this.auth?.password) {
|
|
13
13
|
throw new Error("BrowserStack credentials are not set for AccessibilityScanner.");
|
|
14
14
|
}
|
|
@@ -43,13 +43,14 @@ export class AccessibilityScanner {
|
|
|
43
43
|
return url;
|
|
44
44
|
}
|
|
45
45
|
});
|
|
46
|
-
const scanSettings = Object.fromEntries(Object.entries(settings).filter(([, value]) => value !== undefined));
|
|
47
46
|
const baseRequestBody = {
|
|
48
47
|
name,
|
|
49
48
|
urlList: transformedUrlList,
|
|
50
49
|
recurring: false,
|
|
51
50
|
...(authConfigId && { authConfigId }),
|
|
52
|
-
...(
|
|
51
|
+
...(advancedRules !== undefined && {
|
|
52
|
+
scanSettings: { advancedRules },
|
|
53
|
+
}),
|
|
53
54
|
};
|
|
54
55
|
let requestBody = baseRequestBody;
|
|
55
56
|
if (hasLocal) {
|
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import { APP_DEVICE_CONFIGS, AppSDKSupportedTestingFrameworkEnum, DEFAULT_APP_PATH, USERNAME_PLACEHOLDER, ACCESS_KEY_PLACEHOLDER, createStep, } from "./index.js";
|
|
2
|
-
/**
|
|
3
|
-
* The App Accessibility settings written when checks are on. Only the WCAG version is
|
|
4
|
-
* spelled out, as the one most often changed; the SDK's other defaults apply.
|
|
5
|
-
*/
|
|
6
|
-
const ACCESSIBILITY_OPTIONS = `accessibilityOptions:
|
|
7
|
-
wcagVersion: wcag22aa
|
|
8
|
-
includeIssueType:
|
|
9
|
-
bestPractice: true`;
|
|
10
2
|
export function generateAppBrowserStackYMLInstructions(config, appPath = DEFAULT_APP_PATH) {
|
|
11
3
|
if (config.testingFramework ===
|
|
12
4
|
AppSDKSupportedTestingFrameworkEnum.nightwatch ||
|
|
@@ -20,7 +12,6 @@ export function generateAppBrowserStackYMLInstructions(config, appPath = DEFAULT
|
|
|
20
12
|
const buildName = config.projectName
|
|
21
13
|
? `${config.projectName}-AppAutomate-Build`
|
|
22
14
|
: "bstack-demo";
|
|
23
|
-
// A YAML comment, not "//": YAML reads a "//" line as a key.
|
|
24
15
|
const configContent = `\`\`\`yaml
|
|
25
16
|
userName: ${USERNAME_PLACEHOLDER}
|
|
26
17
|
accessKey: ${ACCESS_KEY_PLACEHOLDER}
|
|
@@ -28,22 +19,21 @@ app: ${appPath}
|
|
|
28
19
|
platforms:
|
|
29
20
|
${platformConfigs}
|
|
30
21
|
parallelsPerPlatform: 1
|
|
31
|
-
browserstackLocal:
|
|
32
|
-
|
|
22
|
+
browserstackLocal: true
|
|
23
|
+
// TODO: replace projectName and buildName according to actual project
|
|
33
24
|
projectName: ${projectName}
|
|
34
25
|
buildName: ${buildName}
|
|
35
26
|
debug: true
|
|
36
27
|
networkLogs: true
|
|
37
28
|
percy: false
|
|
38
29
|
percyCaptureMode: auto
|
|
39
|
-
accessibility:
|
|
30
|
+
accessibility: false
|
|
40
31
|
\`\`\`
|
|
41
32
|
|
|
42
33
|
**Important notes:**
|
|
43
34
|
- Replace \`app: ${appPath}\` with the path to your actual app file (e.g., \`./SampleApp.apk\` for Android or \`./SampleApp.ipa\` for iOS)
|
|
44
35
|
- You can upload your app using BrowserStack's App Upload API or manually through the dashboard
|
|
45
36
|
- Set \`browserstackLocal: true\` if you need to test with local/staging servers
|
|
46
|
-
- Keep \`osVersion\` unquoted: the SDK parses it as a number, and its App Accessibility module reads that key (not \`platformVersion\`)
|
|
47
37
|
- Adjust \`parallelsPerPlatform\` based on your subscription limits`;
|
|
48
38
|
const stepTitle = "Update browserstack.yml file with App Automate configuration:";
|
|
49
39
|
const stepDescription = `Create or update the browserstack.yml file in your project root with the following content:
|
|
@@ -57,7 +47,7 @@ function generatePlatformConfigs(config) {
|
|
|
57
47
|
.map((env) => {
|
|
58
48
|
return ` - platformName: ${env.platform}
|
|
59
49
|
deviceName: "${env.deviceName}"
|
|
60
|
-
|
|
50
|
+
platformVersion: "${env.osVersion}"`;
|
|
61
51
|
})
|
|
62
52
|
.join("\n");
|
|
63
53
|
}
|
|
@@ -70,7 +60,7 @@ function generatePlatformConfigs(config) {
|
|
|
70
60
|
return devices
|
|
71
61
|
.map((device) => ` - platformName: ${platform}
|
|
72
62
|
deviceName: ${device.deviceName}
|
|
73
|
-
|
|
63
|
+
platformVersion: "${device.platformVersion}"`)
|
|
74
64
|
.join("\n");
|
|
75
65
|
})
|
|
76
66
|
.filter(Boolean)
|
|
@@ -21,7 +21,7 @@ export declare const MobileDeviceSchema: z.ZodObject<{
|
|
|
21
21
|
deviceName: z.ZodString;
|
|
22
22
|
osVersion: z.ZodString;
|
|
23
23
|
}, z.core.$strip>;
|
|
24
|
-
export declare const SETUP_APP_AUTOMATE_DESCRIPTION = "Set up BrowserStack App Automate SDK integration for Appium-based mobile app testing. ONLY for Appium based framework . This tool configures SDK for various languages with appium. For pre-built Espresso or XCUITest test suites, use 'runAppTestsOnBrowserStack' instead.
|
|
24
|
+
export declare const SETUP_APP_AUTOMATE_DESCRIPTION = "Set up BrowserStack App Automate SDK integration for Appium-based mobile app testing. ONLY for Appium based framework . This tool configures SDK for various languages with appium. For pre-built Espresso or XCUITest test suites, use 'runAppTestsOnBrowserStack' instead.";
|
|
25
25
|
export declare const SETUP_APP_AUTOMATE_SCHEMA: {
|
|
26
26
|
detectedFramework: z.ZodEnum<typeof AppSDKSupportedFrameworkEnum>;
|
|
27
27
|
detectedTestingFramework: z.ZodEnum<typeof AppSDKSupportedTestingFrameworkEnum>;
|
|
@@ -36,5 +36,4 @@ export declare const SETUP_APP_AUTOMATE_SCHEMA: {
|
|
|
36
36
|
}, z.core.$strip>>>;
|
|
37
37
|
appPath: z.ZodString;
|
|
38
38
|
project: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
39
|
-
accessibility: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
40
39
|
};
|
|
@@ -31,7 +31,7 @@ export const MobileDeviceSchema = z.object({
|
|
|
31
31
|
osVersion: z.string().describe("OS version, e.g. '14', '16', '17', 'latest'"),
|
|
32
32
|
});
|
|
33
33
|
// Tool description and schema for setupBrowserStackAppAutomateTests
|
|
34
|
-
export const SETUP_APP_AUTOMATE_DESCRIPTION = "Set up BrowserStack App Automate SDK integration for Appium-based mobile app testing. ONLY for Appium based framework . This tool configures SDK for various languages with appium. For pre-built Espresso or XCUITest test suites, use 'runAppTestsOnBrowserStack' instead.
|
|
34
|
+
export const SETUP_APP_AUTOMATE_DESCRIPTION = "Set up BrowserStack App Automate SDK integration for Appium-based mobile app testing. ONLY for Appium based framework . This tool configures SDK for various languages with appium. For pre-built Espresso or XCUITest test suites, use 'runAppTestsOnBrowserStack' instead.";
|
|
35
35
|
export const SETUP_APP_AUTOMATE_SCHEMA = {
|
|
36
36
|
detectedFramework: z
|
|
37
37
|
.nativeEnum(AppSDKSupportedFrameworkEnum)
|
|
@@ -55,9 +55,4 @@ export const SETUP_APP_AUTOMATE_SCHEMA = {
|
|
|
55
55
|
.optional()
|
|
56
56
|
.default("BStack-AppAutomate-Suite")
|
|
57
57
|
.describe("Project name for organizing test runs on BrowserStack."),
|
|
58
|
-
accessibility: z
|
|
59
|
-
.boolean()
|
|
60
|
-
.optional()
|
|
61
|
-
.default(false)
|
|
62
|
-
.describe("Run App Accessibility (WCAG) checks on the screens the tests touch; Android 11+ or iOS 14+."),
|
|
63
58
|
};
|
|
@@ -4,7 +4,6 @@ import { validateAppAutomateDevices } from "../../sdk-utils/common/device-valida
|
|
|
4
4
|
import { CREDENTIALS_SUBSTITUTION_NOTE } from "../../sdk-utils/common/credentials.js";
|
|
5
5
|
import { getAppUploadInstruction, validateSupportforAppAutomate, } from "./utils.js";
|
|
6
6
|
import { getAppSDKPrefixCommand, generateAppBrowserStackYMLInstructions, } from "./index.js";
|
|
7
|
-
import { getAppAccessibilityChecksInstructions, getAppAccessibilityNotSetUpInstructions, } from "./languages/accessibility.js";
|
|
8
7
|
import { createStep, formatAppInstructionsWithNumbers, getAppInstructionsForProjectConfiguration, SETUP_APP_AUTOMATE_SCHEMA, } from "./index.js";
|
|
9
8
|
export async function setupAppAutomateHandler(rawInput, config) {
|
|
10
9
|
const input = z.object(SETUP_APP_AUTOMATE_SCHEMA).parse(rawInput);
|
|
@@ -26,7 +25,6 @@ export async function setupAppAutomateHandler(rawInput, config) {
|
|
|
26
25
|
]) ?? [];
|
|
27
26
|
const appPath = input.appPath;
|
|
28
27
|
const framework = input.detectedFramework;
|
|
29
|
-
const accessibility = input.accessibility === true;
|
|
30
28
|
//Validating if supported framework or not
|
|
31
29
|
validateSupportforAppAutomate(framework, language, testingFramework);
|
|
32
30
|
// Use default mobile devices when array is empty
|
|
@@ -38,7 +36,7 @@ export async function setupAppAutomateHandler(rawInput, config) {
|
|
|
38
36
|
// Extract platforms for backward compatibility (if needed)
|
|
39
37
|
const platforms = validatedEnvironments.map((env) => env.platform);
|
|
40
38
|
// Step 1: Generate SDK setup command
|
|
41
|
-
const sdkCommand = getAppSDKPrefixCommand(language, testingFramework, appPath
|
|
39
|
+
const sdkCommand = getAppSDKPrefixCommand(language, testingFramework, appPath);
|
|
42
40
|
if (sdkCommand) {
|
|
43
41
|
instructions.push({ content: sdkCommand, type: "setup" });
|
|
44
42
|
}
|
|
@@ -48,7 +46,6 @@ export async function setupAppAutomateHandler(rawInput, config) {
|
|
|
48
46
|
platforms,
|
|
49
47
|
testingFramework,
|
|
50
48
|
projectName: input.project,
|
|
51
|
-
accessibility,
|
|
52
49
|
}, appPath);
|
|
53
50
|
if (configInstructions) {
|
|
54
51
|
instructions.push({ content: configInstructions, type: "config" });
|
|
@@ -63,14 +60,6 @@ export async function setupAppAutomateHandler(rawInput, config) {
|
|
|
63
60
|
if (projectInstructions) {
|
|
64
61
|
instructions.push({ content: projectInstructions, type: "run" });
|
|
65
62
|
}
|
|
66
|
-
// Step 5: What makes an accessibility scan happen, and how to read the issues
|
|
67
|
-
if (accessibility) {
|
|
68
|
-
instructions.push({
|
|
69
|
-
content: getAppAccessibilityNotSetUpInstructions(testingFramework) ??
|
|
70
|
-
getAppAccessibilityChecksInstructions(language, testingFramework),
|
|
71
|
-
type: "run",
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
63
|
const combinedInstructions = instructions
|
|
75
64
|
.map((instruction) => instruction.content)
|
|
76
65
|
.join("\n\n");
|
|
@@ -1,5 +1,3 @@
|
|
|
1
1
|
import { AppSDKSupportedLanguage, AppSDKSupportedTestingFramework } from "./index.js";
|
|
2
2
|
export declare function getAppInstructionsForProjectConfiguration(framework: string, testingFramework: AppSDKSupportedTestingFramework, language: AppSDKSupportedLanguage): string;
|
|
3
|
-
export declare function getAppSDKPrefixCommand(language: AppSDKSupportedLanguage, testingFramework: string, appPath?: string
|
|
4
|
-
accessibility?: boolean;
|
|
5
|
-
}): string;
|
|
3
|
+
export declare function getAppSDKPrefixCommand(language: AppSDKSupportedLanguage, testingFramework: string, appPath?: string): string;
|
|
@@ -29,14 +29,14 @@ export function getAppInstructionsForProjectConfiguration(framework, testingFram
|
|
|
29
29
|
return "";
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
export function getAppSDKPrefixCommand(language, testingFramework, appPath
|
|
32
|
+
export function getAppSDKPrefixCommand(language, testingFramework, appPath) {
|
|
33
33
|
switch (language) {
|
|
34
34
|
case "csharp":
|
|
35
35
|
return getCSharpSDKCommand();
|
|
36
36
|
case "java":
|
|
37
37
|
return getJavaSDKCommand(testingFramework, appPath);
|
|
38
38
|
case "nodejs":
|
|
39
|
-
return getNodejsSDKCommand(testingFramework
|
|
39
|
+
return getNodejsSDKCommand(testingFramework);
|
|
40
40
|
case "python":
|
|
41
41
|
return getPythonSDKCommand(testingFramework);
|
|
42
42
|
case "ruby":
|
|
@@ -1,5 +1,3 @@
|
|
|
1
1
|
import { AppSDKSupportedTestingFramework } from "../index.js";
|
|
2
|
-
export declare function getNodejsSDKCommand(testingFramework: string
|
|
3
|
-
accessibility?: boolean;
|
|
4
|
-
}): string;
|
|
2
|
+
export declare function getNodejsSDKCommand(testingFramework: string): string;
|
|
5
3
|
export declare function getNodejsAppInstructions(testingFramework: AppSDKSupportedTestingFramework): string;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Node.js instructions and commands for App SDK utilities
|
|
2
2
|
import { AppSDKSupportedTestingFrameworkEnum, createStep, combineInstructions, } from "../index.js";
|
|
3
|
-
export function getNodejsSDKCommand(testingFramework
|
|
3
|
+
export function getNodejsSDKCommand(testingFramework) {
|
|
4
4
|
switch (testingFramework) {
|
|
5
5
|
case "webdriverio":
|
|
6
|
-
return getWebDriverIOCommand(
|
|
6
|
+
return getWebDriverIOCommand();
|
|
7
7
|
case "nightwatch":
|
|
8
8
|
return getNightwatchCommand();
|
|
9
9
|
case "jest":
|
|
@@ -39,14 +39,7 @@ export function getNodejsAppInstructions(testingFramework) {
|
|
|
39
39
|
return "";
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
function getWebDriverIOCommand(
|
|
43
|
-
const accessibilityConfig = accessibility
|
|
44
|
-
? `accessibility: true,
|
|
45
|
-
accessibilityOptions: {
|
|
46
|
-
wcagVersion: 'wcag22aa',
|
|
47
|
-
includeIssueType: { bestPractice: true },
|
|
48
|
-
},`
|
|
49
|
-
: "accessibility: false,";
|
|
42
|
+
function getWebDriverIOCommand() {
|
|
50
43
|
const prerequisiteStep = createStep("Prerequisite Setup:", `a. Ensure you do not modify or replace any existing local driver code,
|
|
51
44
|
as it will be automatically managed and overwritten by the BrowserStack SDK/Driver.
|
|
52
45
|
b. Do not create any YML file in this integration as it is not required.
|
|
@@ -55,13 +48,7 @@ function getWebDriverIOCommand({ accessibility = false, } = {}) {
|
|
|
55
48
|
export BROWSERSTACK_USERNAME="<your_browserstack_username>"
|
|
56
49
|
export BROWSERSTACK_ACCESS_KEY="<your_browserstack_access_key>"
|
|
57
50
|
\`\`\``);
|
|
58
|
-
|
|
59
|
-
const installStep = createStep("Install BrowserStack WDIO service:", accessibility
|
|
60
|
-
? `\`\`\`bash
|
|
61
|
-
npm install @wdio/browserstack-service@^9.6.0 --save-dev
|
|
62
|
-
\`\`\`
|
|
63
|
-
App Accessibility needs WebdriverIO v8 or v9: on WebdriverIO v8, install @wdio/browserstack-service@^8.42.0 instead.`
|
|
64
|
-
: `\`\`\`bash
|
|
51
|
+
const installStep = createStep("Install BrowserStack WDIO service:", `\`\`\`bash
|
|
65
52
|
npm install @wdio/browserstack-service@^7 --save-dev
|
|
66
53
|
\`\`\``);
|
|
67
54
|
const configStep = createStep("Update your WebdriverIO config file (e.g., \\`wdio.conf.js\\`) to add the BrowserStack service and capabilities:", `\`\`\`js
|
|
@@ -75,7 +62,7 @@ exports.config = {
|
|
|
75
62
|
{
|
|
76
63
|
app: 'bs://sample.app',
|
|
77
64
|
browserstackLocal: true,
|
|
78
|
-
|
|
65
|
+
accessibility: false,
|
|
79
66
|
testObservabilityOptions: {
|
|
80
67
|
buildName: "bstack-demo",
|
|
81
68
|
projectName: "BrowserStack Sample",
|
|
@@ -87,7 +74,7 @@ exports.config = {
|
|
|
87
74
|
capabilities: [{
|
|
88
75
|
'bstack:options': {
|
|
89
76
|
deviceName: 'Samsung Galaxy S22 Ultra',
|
|
90
|
-
|
|
77
|
+
platformVersion: '12.0',
|
|
91
78
|
platformName: 'android',
|
|
92
79
|
}
|
|
93
80
|
}],
|
|
@@ -38,11 +38,6 @@ export declare function uploadEspressoApp(appPath: string, config: BrowserStackC
|
|
|
38
38
|
export declare function uploadEspressoTestSuite(testSuitePath: string, config: BrowserStackConfig): Promise<string>;
|
|
39
39
|
export declare function uploadXcuiApp(appPath: string, config: BrowserStackConfig): Promise<string>;
|
|
40
40
|
export declare function uploadXcuiTestSuite(testSuitePath: string, config: BrowserStackConfig): Promise<string>;
|
|
41
|
-
|
|
42
|
-
export
|
|
43
|
-
/** Run App Accessibility checks during the tests (the build API's `accessibility` flag). */
|
|
44
|
-
accessibility?: boolean;
|
|
45
|
-
}
|
|
46
|
-
export declare function triggerEspressoBuild(app_url: string, test_suite_url: string, devices: string[], project: string, config: BrowserStackConfig, options?: NativeBuildOptions): Promise<string>;
|
|
47
|
-
export declare function triggerXcuiBuild(app_url: string, test_suite_url: string, devices: string[], project: string, config: BrowserStackConfig, options?: NativeBuildOptions): Promise<string>;
|
|
41
|
+
export declare function triggerEspressoBuild(app_url: string, test_suite_url: string, devices: string[], project: string, config: BrowserStackConfig): Promise<string>;
|
|
42
|
+
export declare function triggerXcuiBuild(app_url: string, test_suite_url: string, devices: string[], project: string, config: BrowserStackConfig): Promise<string>;
|
|
48
43
|
export {};
|