Skip to content

Commit c24df25

Browse files
added server fixes
1 parent ecb295a commit c24df25

File tree

4 files changed

+513
-25
lines changed

4 files changed

+513
-25
lines changed

bin/accessibility-automation/cypress/index.js

Lines changed: 104 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,51 @@ const browserStackLog = (message) => {
44
if (!Cypress.env('BROWSERSTACK_LOGS')) return;
55
cy.task('browserstack_log', message);
66
}
7-
8-
const commandsToWrap = ['visit', 'click', 'type', 'request', 'dblclick', 'rightclick', 'clear', 'check', 'uncheck', 'select', 'trigger', 'selectFile', 'scrollIntoView', 'scroll', 'scrollTo', 'blur', 'focus', 'go', 'reload', 'submit', 'viewport', 'origin'];
9-
// scroll is not a default function in cypress.
10-
const commandToOverwrite = ['visit', 'click', 'type', 'request', 'dblclick', 'rightclick', 'clear', 'check', 'uncheck', 'select', 'trigger', 'selectFile', 'scrollIntoView', 'scrollTo', 'blur', 'focus', 'go', 'reload', 'submit', 'viewport', 'origin'];
7+
8+
// Default commands (fallback)
9+
const defaultCommandsToWrap = ['visit', 'click', 'type', 'request', 'dblclick', 'rightclick', 'clear', 'check', 'uncheck', 'select', 'trigger', 'selectFile', 'scrollIntoView', 'scroll', 'scrollTo', 'blur', 'focus', 'go', 'reload', 'submit', 'viewport', 'origin'];
10+
11+
// Determine effective commands based on server response
12+
let effectiveCommandsToWrap = defaultCommandsToWrap;
13+
let isBuildEndOnlyMode = false;
14+
15+
// Check if server provided specific commands via environment variables
16+
if (Cypress.env('ACCESSIBILITY_BUILD_END_ONLY') === 'true') {
17+
// Server explicitly wants build-end-only scanning
18+
effectiveCommandsToWrap = [];
19+
isBuildEndOnlyMode = true;
20+
browserStackLog('[A11Y] Server enabled build-end-only mode - disabling all command scanning');
21+
} else if (Cypress.env('ACCESSIBILITY_COMMANDS_TO_WRAP')) {
22+
try {
23+
const serverCommands = JSON.parse(Cypress.env('ACCESSIBILITY_COMMANDS_TO_WRAP'));
24+
25+
if (Array.isArray(serverCommands)) {
26+
if (serverCommands.length === 0) {
27+
// Empty array = build-end only
28+
effectiveCommandsToWrap = [];
29+
isBuildEndOnlyMode = true;
30+
browserStackLog('[A11Y] Server provided empty commands - enabling build-end-only mode');
31+
} else {
32+
// Use server-provided command list
33+
effectiveCommandsToWrap = serverCommands.map(cmd => cmd.name || cmd);
34+
isBuildEndOnlyMode = false;
35+
browserStackLog(`[A11Y] Using server commands: ${effectiveCommandsToWrap.join(', ')}`);
36+
}
37+
}
38+
} catch (error) {
39+
browserStackLog(`[A11Y] Error parsing server commands, using defaults: ${error.message}`);
40+
}
41+
} else {
42+
browserStackLog('[A11Y] No server commands provided, using default command list');
43+
}
44+
45+
// Filter to only include valid Cypress commands
46+
const commandToOverwrite = defaultCommandsToWrap.filter(cmd =>
47+
effectiveCommandsToWrap.includes(cmd)
48+
);
49+
50+
browserStackLog(`[A11Y] Commands to wrap: ${commandToOverwrite.length} out of ${defaultCommandsToWrap.length}`);
51+
browserStackLog(`[A11Y] Build-end-only mode: ${isBuildEndOnlyMode}`);
1152

1253
/*
1354
Overrriding the cypress commands to perform Accessibility Scan before Each command
@@ -50,6 +91,8 @@ new Promise(async (resolve, reject) => {
5091
return resolve();
5192
}
5293

94+
const isBuildEndOnly = Cypress.env('ACCESSIBILITY_BUILD_END_ONLY') === 'true';
95+
5396
function findAccessibilityAutomationElement() {
5497
return win.document.querySelector("#accessibility-automation-element");
5598
}
@@ -82,8 +125,24 @@ new Promise(async (resolve, reject) => {
82125
}
83126

84127
win.addEventListener("A11Y_SCAN_FINISHED", onScanComplete);
85-
const e = new CustomEvent("A11Y_SCAN", { detail: payloadToSend });
86-
win.dispatchEvent(e);
128+
129+
// Enhanced event with mode information and server scripts
130+
const scanEvent = new CustomEvent("A11Y_SCAN", {
131+
detail: {
132+
...payloadToSend,
133+
scanMode: isBuildEndOnlyMode ? "comprehensive-build-end" : "incremental",
134+
timestamp: Date.now(),
135+
serverScripts: Cypress.env('ACCESSIBILITY_SCRIPTS') || null
136+
}
137+
});
138+
139+
if (isBuildEndOnlyMode) {
140+
browserStackLog(`[A11Y] Starting comprehensive build-end scan`);
141+
} else {
142+
browserStackLog(`[A11Y] Starting incremental scan`);
143+
}
144+
145+
win.dispatchEvent(scanEvent);
87146
}
88147

89148
if (findAccessibilityAutomationElement()) {
@@ -299,29 +358,45 @@ const shouldScanForAccessibility = (attributes) => {
299358
return shouldScanTestForAccessibility;
300359
}
301360

302-
commandToOverwrite.forEach((command) => {
303-
Cypress.Commands.overwrite(command, (originalFn, ...args) => {
304-
const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest || Cypress.mocha.getRunner().suite.ctx._runnable;
305-
const shouldScanTestForAccessibility = shouldScanForAccessibility(attributes);
306-
const state = cy.state('current'), Subject = 'getSubjectFromChain' in cy;
307-
const stateName = state === null || state === void 0 ? void 0 : state.get('name');
308-
let stateType = null;
309-
if (!shouldScanTestForAccessibility || (stateName && stateName !== command)) {
310-
return originalFn(...args);
311-
}
312-
if(state !== null && state !== void 0){
313-
stateType = state.get('type');
314-
}
315-
performModifiedScan(originalFn, Subject, stateType, ...args);
316-
});
317-
});
361+
// Only wrap commands if not in build-end-only mode and we have commands to wrap
362+
if (!isBuildEndOnlyMode && commandToOverwrite.length > 0) {
363+
browserStackLog(`[A11Y] Wrapping ${commandToOverwrite.length} commands for accessibility scanning`);
364+
365+
commandToOverwrite.forEach((command) => {
366+
Cypress.Commands.overwrite(command, (originalFn, ...args) => {
367+
const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest || Cypress.mocha.getRunner().suite.ctx._runnable;
368+
const shouldScanTestForAccessibility = shouldScanForAccessibility(attributes);
369+
const state = cy.state('current'), Subject = 'getSubjectFromChain' in cy;
370+
const stateName = state === null || state === void 0 ? void 0 : state.get('name');
371+
let stateType = null;
372+
if (!shouldScanTestForAccessibility || (stateName && stateName !== command)) {
373+
return originalFn(...args);
374+
}
375+
if(state !== null && state !== void 0){
376+
stateType = state.get('type');
377+
}
378+
379+
browserStackLog(`[A11Y] Performing command-level scan for: ${command}`);
380+
performModifiedScan(originalFn, Subject, stateType, ...args);
381+
});
382+
});
383+
384+
browserStackLog(`[A11Y] Successfully wrapped ${commandToOverwrite.length} commands for accessibility scanning`);
385+
} else {
386+
browserStackLog(`[A11Y] Command wrapping disabled - using build-end-only scanning mode`);
387+
}
318388

319389
afterEach(() => {
320390
const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest;
321391
cy.window().then(async (win) => {
322392
let shouldScanTestForAccessibility = shouldScanForAccessibility(attributes);
323393
if (!shouldScanTestForAccessibility) return cy.wrap({});
324394

395+
// Determine current scanning mode
396+
const currentMode = isBuildEndOnlyMode ? 'build-end-only' : 'command-plus-end';
397+
browserStackLog(`[A11Y] Starting final scan in ${currentMode} mode`);
398+
399+
// Perform final scan (this happens regardless of mode)
325400
cy.wrap(performScan(win), {timeout: 30000}).then(() => {
326401
try {
327402
let os_data;
@@ -347,13 +422,17 @@ afterEach(() => {
347422
const payloadToSend = {
348423
"thTestRunUuid": testRunUuid,
349424
"thBuildUuid": Cypress.env("BROWSERSTACK_TESTHUB_UUID"),
350-
"thJwtToken": Cypress.env("BROWSERSTACK_TESTHUB_JWT")
425+
"thJwtToken": Cypress.env("BROWSERSTACK_TESTHUB_JWT"),
426+
"scanMode": currentMode,
427+
"buildEndOnly": isBuildEndOnlyMode
351428
};
352-
browserStackLog(`Payload to send: ${JSON.stringify(payloadToSend)}`);
429+
430+
browserStackLog(`[A11Y] Saving results for ${currentMode} mode`);
431+
browserStackLog(`[A11Y] Payload: ${JSON.stringify(payloadToSend)}`);
353432

354433
return cy.wrap(saveTestResults(win, payloadToSend), {timeout: 30000});
355434
}).then(() => {
356-
browserStackLog(`Saved accessibility test results`);
435+
browserStackLog(`[A11Y] Successfully completed ${currentMode} accessibility scanning and saved results`);
357436
})
358437

359438
} catch (er) {

bin/accessibility-automation/helper.js

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const glob = require('glob');
99
const helper = require('../helpers/helper');
1010
const { CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS } = require('../helpers/constants');
1111
const { consoleHolder } = require("../testObservability/helper/constants");
12+
const scripts = require('./scripts');
1213
const supportFileContentMap = {}
1314
const HttpsProxyAgent = require('https-proxy-agent');
1415

@@ -273,3 +274,169 @@ exports.setAccessibilityEventListeners = (bsConfig) => {
273274
logger.debug(`Unable to parse support files to set event listeners with error ${e}`, true, e);
274275
}
275276
}
277+
278+
// Process server accessibility configuration similar to Node Agent
279+
exports.processServerAccessibilityConfig = (responseData) => {
280+
logger.debug('[A11Y] Processing server accessibility configuration');
281+
282+
try {
283+
// Use Scripts class to parse server response
284+
scripts.parseFromResponse(responseData);
285+
286+
// Handle the commandsToWrap structure from the server response
287+
if (responseData.accessibility?.options?.commandsToWrap) {
288+
const commandsToWrapData = responseData.accessibility.options.commandsToWrap;
289+
290+
// Extract the actual commands array from the nested structure
291+
const serverCommands = commandsToWrapData.commands || [];
292+
293+
// Store server commands for Cypress to read
294+
process.env.ACCESSIBILITY_COMMANDS_TO_WRAP = JSON.stringify(serverCommands);
295+
296+
logger.debug(`[A11Y] Server provided ${serverCommands.length} commands for wrapping`);
297+
298+
if (serverCommands.length === 0) {
299+
logger.debug('[A11Y] Server wants build-end-only scanning - command wrapping will be disabled');
300+
process.env.ACCESSIBILITY_BUILD_END_ONLY = 'true';
301+
} else {
302+
logger.debug(`[A11Y] Server wants command-level scanning for: ${serverCommands.map(cmd => cmd.name || cmd).join(', ')}`);
303+
process.env.ACCESSIBILITY_BUILD_END_ONLY = 'false';
304+
}
305+
306+
// Also store scriptsToRun if available
307+
if (commandsToWrapData.scriptsToRun) {
308+
process.env.ACCESSIBILITY_SCRIPTS_TO_RUN = JSON.stringify(commandsToWrapData.scriptsToRun);
309+
logger.debug(`[A11Y] Server provided scripts to run: ${commandsToWrapData.scriptsToRun.join(', ')}`);
310+
}
311+
} else {
312+
logger.debug('[A11Y] No server commands provided, using default command list');
313+
process.env.ACCESSIBILITY_BUILD_END_ONLY = 'false';
314+
}
315+
316+
// Process scripts from server response
317+
if (responseData.accessibility?.options?.scripts) {
318+
const serverScripts = responseData.accessibility.options.scripts;
319+
320+
// Convert array of script objects to a map for easier access
321+
const scriptsMap = {};
322+
serverScripts.forEach(script => {
323+
scriptsMap[script.name] = script.command;
324+
});
325+
326+
// Store server scripts for Cypress to read
327+
process.env.ACCESSIBILITY_SCRIPTS = JSON.stringify(scriptsMap);
328+
329+
logger.debug(`[A11Y] Server provided accessibility scripts: ${Object.keys(scriptsMap).join(', ')}`);
330+
} else {
331+
logger.debug('[A11Y] No server scripts provided, using default scripts');
332+
}
333+
334+
// Process capabilities for token and other settings
335+
if (responseData.accessibility?.options?.capabilities) {
336+
const capabilities = responseData.accessibility.options.capabilities;
337+
338+
capabilities.forEach(cap => {
339+
if (cap.name === 'accessibilityToken') {
340+
process.env.BS_A11Y_JWT = cap.value;
341+
logger.debug('[A11Y] Set accessibility token from server response');
342+
} else if (cap.name === 'test_run_id') {
343+
process.env.BS_A11Y_TEST_RUN_ID = cap.value;
344+
logger.debug('[A11Y] Set test run ID from server response');
345+
} else if (cap.name === 'testhub_build_uuid') {
346+
process.env.BROWSERSTACK_TESTHUB_UUID = cap.value;
347+
logger.debug('[A11Y] Set TestHub build UUID from server response');
348+
} else if (cap.name === 'scannerVersion') {
349+
process.env.ACCESSIBILITY_SCANNERVERSION = cap.value;
350+
logger.debug('[A11Y] Set scanner version from server response');
351+
}
352+
});
353+
}
354+
355+
logger.debug('[A11Y] Successfully processed server accessibility configuration');
356+
} catch (error) {
357+
logger.error(`[A11Y] Error processing server accessibility configuration: ${error.message}`);
358+
// Fallback to default behavior
359+
process.env.ACCESSIBILITY_BUILD_END_ONLY = 'false';
360+
}
361+
};
362+
363+
// Check if command should be wrapped based on server response
364+
exports.shouldWrapCommand = (commandName) => {
365+
try {
366+
if (!commandName) {
367+
return false;
368+
}
369+
370+
// Check if we're in build-end-only mode
371+
if (process.env.ACCESSIBILITY_BUILD_END_ONLY === 'true') {
372+
logger.debug(`[A11Y] Build-end-only mode: not wrapping command ${commandName}`);
373+
return false;
374+
}
375+
376+
// Use Scripts class to check if command should be wrapped
377+
const shouldWrap = scripts.shouldWrapCommand(commandName);
378+
379+
// If Scripts class has no commands configured, fallback to checking environment
380+
if (!shouldWrap && process.env.ACCESSIBILITY_COMMANDS_TO_WRAP) {
381+
const serverCommands = JSON.parse(process.env.ACCESSIBILITY_COMMANDS_TO_WRAP);
382+
383+
if (Array.isArray(serverCommands) && serverCommands.length > 0) {
384+
const envShouldWrap = serverCommands.some(command => {
385+
return (command.name || command).toLowerCase() === commandName.toLowerCase();
386+
});
387+
388+
logger.debug(`[A11Y] shouldWrapCommand: ${commandName} -> ${envShouldWrap} (env-driven)`);
389+
return envShouldWrap;
390+
}
391+
}
392+
393+
// If we got a result from Scripts class, use it
394+
if (scripts.commandsToWrap && scripts.commandsToWrap.length > 0) {
395+
logger.debug(`[A11Y] shouldWrapCommand: ${commandName} -> ${shouldWrap} (scripts-driven)`);
396+
return shouldWrap;
397+
}
398+
399+
// Fallback to default commands if no server commands
400+
const defaultCommands = ['visit', 'click', 'type', 'request', 'dblclick', 'rightclick', 'clear', 'check', 'uncheck', 'select', 'trigger', 'selectFile', 'scrollIntoView', 'scroll', 'scrollTo', 'blur', 'focus', 'go', 'reload', 'submit', 'viewport', 'origin'];
401+
const defaultShouldWrap = defaultCommands.includes(commandName.toLowerCase());
402+
403+
logger.debug(`[A11Y] shouldWrapCommand: ${commandName} -> ${defaultShouldWrap} (default)`);
404+
return defaultShouldWrap;
405+
} catch (error) {
406+
logger.debug(`[A11Y] Error in shouldWrapCommand: ${error.message}`);
407+
return false;
408+
}
409+
};
410+
411+
// Get accessibility script by name
412+
exports.getAccessibilityScript = (scriptName) => {
413+
try {
414+
// Try to get script from Scripts class first
415+
const script = scripts.getScript(scriptName);
416+
417+
if (script) {
418+
logger.debug(`[A11Y] Retrieved script '${scriptName}' from Scripts class`);
419+
return script;
420+
}
421+
422+
// Fallback to environment variable
423+
if (process.env.ACCESSIBILITY_SCRIPTS) {
424+
const serverScripts = JSON.parse(process.env.ACCESSIBILITY_SCRIPTS);
425+
const envScript = serverScripts[scriptName] || serverScripts[scriptName.toLowerCase()];
426+
427+
if (envScript) {
428+
logger.debug(`[A11Y] Retrieved script '${scriptName}' from environment`);
429+
return envScript;
430+
}
431+
}
432+
433+
logger.debug(`[A11Y] Script '${scriptName}' not found`);
434+
return null;
435+
} catch (error) {
436+
logger.error(`[A11Y] Error retrieving script '${scriptName}': ${error.message}`);
437+
return null;
438+
}
439+
};
440+
441+
// Export the Scripts instance for direct access
442+
exports.scripts = scripts;

0 commit comments

Comments
 (0)