Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDK 577 Implement changes for a11y stability #31

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 18 additions & 10 deletions nightwatch/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ module.exports = {
registerEventHandlers(eventBroadcaster) {

eventBroadcaster.on('TestCaseStarted', async (args) => {
if (!helper.isTestObservabilitySession()) {
if (!helper.isTestObservabilitySession() && !helper.isAccessibilitySession()) {
return;
}
try {
Expand Down Expand Up @@ -133,7 +133,7 @@ module.exports = {
});

eventBroadcaster.on('TestCaseFinished', async (args) => {
if (!helper.isTestObservabilitySession()) {
if (!helper.isTestObservabilitySession() && !helper.isAccessibilitySession()) {
return;
}
try {
Expand All @@ -147,7 +147,9 @@ module.exports = {
if (testMetaData) {
delete _tests[testCaseId];
testMetaData.finishedAt = new Date().toISOString();
await testObservability.sendTestRunEventForCucumber(reportData, gherkinDocument, pickleData, 'TestRunFinished', testMetaData, args);
if (helper.isTestObservabilitySession()) {
await testObservability.sendTestRunEventForCucumber(reportData, gherkinDocument, pickleData, 'TestRunFinished', testMetaData, args);
}
}
} catch (error) {
CrashReporter.uploadCrashReport(error.message, error.stack);
Expand All @@ -156,7 +158,7 @@ module.exports = {
});

eventBroadcaster.on('TestStepStarted', async (args) => {
if (!helper.isTestObservabilitySession()) {
if (!helper.isTestObservabilitySession() && !helper.isAccessibilitySession()) {
return;
}
try {
Expand Down Expand Up @@ -189,7 +191,7 @@ module.exports = {
});

eventBroadcaster.on('TestStepFinished', async (args) => {
if (!helper.isTestObservabilitySession()) {
if (!helper.isTestObservabilitySession() && !helper.isAccessibilitySession()) {
return;
}
try {
Expand Down Expand Up @@ -298,7 +300,6 @@ module.exports = {
if (helper.isCucumberTestSuite(settings)) {
cucumberPatcher();
process.env.CUCUMBER_SUITE = 'true';
settings.test_runner.options['require'] = path.resolve(__dirname, 'observabilityLogPatcherHook.js');
}
settings.globals['customReporterCallbackTimeout'] = CUSTOM_REPORTER_CALLBACK_TIMEOUT;
if (testObservability._user && testObservability._key) {
Expand Down Expand Up @@ -329,6 +330,9 @@ module.exports = {
Logger.error(`Could not configure or launch accessibility automation - ${error}`);
}

if ((helper.isAccessibilitySession() || helper.isTestObservabilitySession()) && helper.isCucumberTestSuite(settings)) {
settings.test_runner.options['require'] = path.resolve(__dirname, 'observabilityLogPatcherHook.js');
}
},

async after() {
Expand All @@ -347,21 +351,25 @@ module.exports = {
} catch (error) {
Logger.error(`Something went wrong in stopping build session for test observability - ${error}`);
}
process.exit();
}
if (helper.isAccessibilitySession()){
try {
await accessibilityAutomation.stopAccessibilityTestRun();
} catch (error) {
Logger.error(`Exception in stop accessibility test run: ${error}`);
}

}
process.exit();
},

async beforeEach(settings) {
browser.getAccessibilityResults = () => { return accessibilityAutomation.getAccessibilityResults() };
browser.getAccessibilityResultsSummary = () => { return accessibilityAutomation.getAccessibilityResultsSummary() };
if (helper.isAccessibilitySession()) {
helper.modifySeleniumCommands();
helper.modifyNightwatchCommands();
browser.getAccessibilityResults = () => { return accessibilityAutomation.getAccessibilityResults() };
browser.getAccessibilityResultsSummary = () => { return accessibilityAutomation.getAccessibilityResultsSummary() };
}

// await accessibilityAutomation.beforeEachExecution(browser);
},

Expand Down
17 changes: 14 additions & 3 deletions nightwatch/observabilityLogPatcherHook.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
try {
const {Before} = require('@cucumber/cucumber');

Before((testCase) => {
const {Before, After} = require('@cucumber/cucumber');
const nightwatchPluginHelper = require('@nightwatch/browserstack/src/utils/helper');
const AccessibilityAutomation = require('@nightwatch/browserstack/src/accessibilityAutomation');

Before(async (testCase) => {
nightwatchPluginHelper.modifySeleniumCommands();
nightwatchPluginHelper.modifyNightwatchCommands();
console.log(`TEST-OBSERVABILITY-PID-TESTCASE-MAPPING-${testCase.testCaseStartedId}`);
const testMeta = nightwatchPluginHelper.getCucumberTestMetaData(testCase);
await AccessibilityAutomation.prototype.beforeEachExecution(testMeta);
});

After(async (testCase) => {
const testMeta = nightwatchPluginHelper.getCucumberTestMetaData(testCase);
await AccessibilityAutomation.prototype.afterEachExecution(testMeta);
});

} catch (error) { /* empty */ }
76 changes: 24 additions & 52 deletions src/accessibilityAutomation.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const {makeRequest} = require('./utils/requestHelper');
const Logger = require('./utils/logger');
const {ACCESSIBILITY_URL} = require('./utils/constants');
const util = require('util');
const scripts = require('./utils/scripts');

class AccessibilityAutomation {
configure(settings = {}) {
Expand Down Expand Up @@ -73,7 +74,10 @@ class AccessibilityAutomation {
source: {
frameworkName: helper.getFrameworkName(this._testRunner),
frameworkVersion: helper.getPackageVersion('nightwatch'),
sdkVersion: helper.getAgentVersion()
sdkVersion: helper.getAgentVersion(),
language: 'javascript',
testFramework: 'selenium',
testFrameworkVersion: helper.getPackageVersion('selenium-webdriver')
},
settings: accessibilityOptions,
versionControl: await helper.getGitMetaData(),
Expand All @@ -91,9 +95,11 @@ class AccessibilityAutomation {
}
};

const response = await makeRequest('POST', 'test_runs', data, config, ACCESSIBILITY_URL);
const response = await makeRequest('POST', 'v2/test_runs', data, config, ACCESSIBILITY_URL);
const responseData = response.data.data || {};

scripts.parseFromJson(responseData);
scripts.toJson();
accessibilityOptions.scannerVersion = responseData.scannerVersion;
process.env.BROWSERSTACK_ACCESSIBILITY_OPTIONS = JSON.stringify(accessibilityOptions);

Expand Down Expand Up @@ -353,11 +359,13 @@ class AccessibilityAutomation {

async beforeEachExecution(testMetaData) {
try {
this.currentTest = browser.currentTest;
this.currentTest = browser.currentTest || {};
this.currentTest.shouldScanTestForAccessibility = this.shouldScanTestForAccessibility(
testMetaData
);
global.shouldScanTestForAccessibility = this.currentTest.shouldScanTestForAccessibility;
this.currentTest.accessibilityScanStarted = true;
global.isAccessibilityPlatform = true;
this._isAccessibilitySession = this.setExtension(browser);

if (this.isAccessibilityAutomationSession() && browser && helper.isAccessibilitySession() && this._isAccessibilitySession) {
Expand All @@ -381,25 +389,6 @@ class AccessibilityAutomation {
Logger.info(
'Setup for Accessibility testing has started. Automate test case execution will begin momentarily.'
);

await browser.executeAsyncScript(`
const callback = arguments[arguments.length - 1];
const fn = () => {
window.addEventListener('A11Y_TAP_STARTED', fn2);
const e = new CustomEvent('A11Y_FORCE_START');
window.dispatchEvent(e);
};
const fn2 = () => {
window.removeEventListener('A11Y_TAP_STARTED', fn);
callback();
}
fn();
`);
} else {
await browser.executeAsyncScript(`
const e = new CustomEvent('A11Y_FORCE_STOP');
window.dispatchEvent(e);
`);
}
}
this.currentTest.accessibilityScanStarted =
Expand All @@ -419,14 +408,19 @@ class AccessibilityAutomation {

async afterEachExecution(testMetaData) {
try {
if (this.currentTest.accessibilityScanStarted && this.isAccessibilityAutomationSession() && this._isAccessibilitySession) {
if (this.currentTest.shouldScanTestForAccessibility) {
const shouldScanTestForAccessibility = this.currentTest ? this.currentTest.shouldScanTestForAccessibility : this.shouldScanTestForAccessibility(
testMetaData
);
const accessibilityScanStarted = this.currentTest ? this.currentTest.accessibilityScanStarted : true;
this._isAccessibilitySession = this.setExtension(browser);
if (accessibilityScanStarted && this.isAccessibilityAutomationSession() && this._isAccessibilitySession) {
if (shouldScanTestForAccessibility) {
Logger.info(
'Automate test case execution has ended. Processing for accessibility testing is underway. '
);
}
const dataForExtension = {
saveResults: this.currentTest.shouldScanTestForAccessibility,
saveResults: shouldScanTestForAccessibility,
testDetails: {
name: testMetaData.testcase,
testRunId: process.env.BS_A11Y_TEST_RUN_ID,
Expand All @@ -435,35 +429,13 @@ class AccessibilityAutomation {
},
platform: await this.fetchPlatformDetails(browser)
};
const final_res = await browser.executeAsyncScript(
`
const callback = arguments[arguments.length - 1];

this.res = null;
if (arguments[0].saveResults) {
window.addEventListener('A11Y_TAP_TRANSPORTER', (event) => {
window.tapTransporterData = event.detail;
this.res = window.tapTransporterData;
callback(this.res);
});
}
const e = new CustomEvent('A11Y_TEST_END', {detail: arguments[0]});
window.dispatchEvent(e);
if (arguments[0].saveResults !== true ) {
callback();
}
`,
dataForExtension
);
if (this.currentTest.shouldScanTestForAccessibility) {
Logger.info('Accessibility testing for this test case has ended.');
}
Logger.debug('Performing scan before saving results');
Logger.debug(util.format(await browser.executeAsyncScript(scripts.performScan, {method: testMetaData.testcase})));
await browser.executeAsyncScript(scripts.saveTestResults, dataForExtension);
Logger.info('Accessibility testing for this test case has ended.');
}
} catch (er) {
Logger.error(
`Accessibility results could not be processed for the test case ${this.currentTest.module}. Error :`,
er
);
Logger.error('Accessibility results could not be processed for the test case. Error: ' + er.toString());
}
}

Expand Down
124 changes: 124 additions & 0 deletions src/utils/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const Logger = require('./logger');
const LogPatcher = require('./logPatcher');
const BSTestOpsPatcher = new LogPatcher({});
const sessions = {};
const scripts = require('./scripts');

console = {};
Object.keys(consoleHolder).forEach(method => {
Expand Down Expand Up @@ -763,3 +764,126 @@ exports.deepClone = (obj) => {
exports.shouldSendLogs = () => {
return exports.isTestObservabilitySession() && exports.isCucumberTestSuite();
};

exports.homedir = () => {
if (typeof os.homedir === 'function') {return os.homedir()}

var env = process.env;
var home = env.HOME;
var user = env.LOGNAME || env.USER || env.LNAME || env.USERNAME;

if (process.platform === 'win32') {
return env.USERPROFILE || env.HOMEDRIVE + env.HOMEPATH || home || null;
}

if (process.platform === 'darwin') {
return home || (user ? '/Users/' + user : null);
}

if (process.platform === 'linux') {
return home || (process.getuid() === 0 ? '/root' : (user ? '/home/' + user : null));
}

return home || null;
};

exports.isBrowserStackCommandExecutor = (parameters) => {
if (parameters && parameters.script && typeof parameters.script === 'string') {
return parameters.script.includes('browserstack_executor');
}

return false;
};

exports.modifySeleniumCommands = () => {
try {
let Executor = exports.requireModule('selenium-webdriver/lib/webdriver.js').WebDriver;
if (!Executor.prototype || !Executor.prototype.execute) {
Executor = exports.requireModule('selenium-webdriver/lib/http.js').Executor;
}

if (Executor.prototype && Executor.prototype.execute) {
const originalExecute = Executor.prototype.execute;
Logger.debug('Modifying webdriver execute');

Executor.prototype.execute = async function() {
exports.performAccessibilityScan({commandType: 'selenium', arguments});

return originalExecute.apply(this, arguments);
};
}
} catch (err) {
Logger.debug('Unable to find executor class ' + err);
}
};

exports.modifyNightwatchCommands = () => {
const modifyClientCommand = (commandMeta) => {
try {
const CommandClass = exports.requireModule(commandMeta.packagePath);
if (CommandClass?.prototype?.performAction) {
const originalCommandClass = CommandClass.prototype.performAction;

CommandClass.prototype.performAction = function(...args) {
exports.performAccessibilityScan({commandType: 'nightwatch', methodName: commandMeta.commandName});

return originalCommandClass.apply(this, ...args);
};
}
} catch (err) {
Logger.debug(`Unable to find executor class from method ${commandMeta.commandName}, Error: ` + err);
}
};

const commandsToModify = [
{
commandName: 'registerBasicAuth',
packagePath: 'nightwatch/lib/api/client-commands/registerBasicAuth.js'
},
{
commandName: 'setDeviceDimensions',
packagePath: 'nightwatch/lib/api/client-commands/setDeviceDimensions.js'
},
{
commandName: 'setGeolocation',
packagePath: 'nightwatch/lib/api/client-commands/setGeolocation.js'
}
];
commandsToModify.forEach(commandMeta => {
modifyClientCommand(commandMeta);
});
};

exports.performAccessibilityScan = (data) => {
let shouldScanCommand = false;
let methodName = '';
if (data.commandType === 'selenium') {
methodName = data.arguments[0].name_;
shouldScanCommand = !global.bstackAllyScanning && global.isAccessibilityPlatform && global.shouldScanTestForAccessibility && scripts.shouldWrapCommand(methodName) && !exports.isBrowserStackCommandExecutor(data.arguments[0].parameters_);
} else if (data.commandType === 'nightwatch') {
methodName = data.methodName;
shouldScanCommand = !global.bstackAllyScanning && global.isAccessibilityPlatform && global.shouldScanTestForAccessibility && scripts.shouldWrapCommand(methodName);
}

try {
if (shouldScanCommand) {
global.bstackAllyScanning = true,
Logger.debug(`Performing scan for ${methodName}`);
browser.executeAsyncScript(scripts.performScan);
}
} catch (er) {
Logger.debug(`Failed to perform scan for ${methodName}, Error: ${er}`);
}
global.bstackAllyScanning = false;
};

exports.getCucumberTestMetaData = (testCase) => {
return {
testcase: testCase.pickle?.name,
metadata: {
name: testCase.gherkinDocument?.feature?.name,
modulePath: path.relative(process.cwd(), testCase.pickle?.uri),
tags: testCase.pickle?.tags?.map(({name}) => (name))
}
};
};
Loading
Loading