How can I test two different test groups concurrently? - testing

I'm trying concurrently run my tests except for tests that include login.
I've tried to separate tests into two groups (with login and without login). These two groups run in parallel. Tests without login are running concurrently with each other, but tests with login run one after each other. The problem is that runner only runs tests without logins twice. I saw .serial feature discussion in https://github.com/DevExpress/testcafe/issues/116 and I think it would help a lot in this situation.
const createTestCafe = require('testcafe');
const config = require('./config');
const testArrayBuilder = require('./Tests/helpers/testArrayBuilder');
let testSteps = require('./Tests/helpers/testSteps');
let testcafe;
const store = process['argv'][2],
name = process['argv'][3],
env = process['argv'][4];
const testsFile = require(`./Tests/${store}/tests.json`);
const output = 'logs/test-results-' + store + '-' + env + '-' + name + '.json';
createTestCafe('localhost', 0)
.then(tc => {
testcafe = tc;
const tests = getTests();
const runner = testcafe.createRunner();
if (name !== 'all') {
runner.filter(testName => {
return testName === name;
});
}
runner.reporter(['spec', {
name: 'json-custom',
output: output
}]);
return Promise.all(tests.map(obj => {
if(obj.login === true) {
//Don't change number value for this one,
//because login tests won't run properly.
return runTests(runner, obj.tests, 1);
} else{
return runTests(runner, obj.tests, 1);
}
}));
})
.then(() => {
testSteps.mergeWithReporter(output);
testcafe.close();
});
const runTests = (runner, tests, windows) => {
return runner
.src(tests)
.browsers(config.browsers)
.concurrency(windows)
.screenshots('logs/screenshots/', true)
.run({
skipJsErrors: true,
quarantineMode: true,
selectorTimeout: 20000,
assertionTimeout: 20000,
pageLoadTimeout: 20000,
speed: 0.5,
stopOnFirstFail: false
});
}
const getTests = () => {
const testsWithoutLogin = testArrayBuilder(testsFile[env], false);
switch (store) {
case 'shop1':
const testsWithLogin = testArrayBuilder(testsFile[env], true);
return [{login: true ,tests: testsWithLogin}, {login: false, tests: testsWithoutLogin}];
case 'shop2':
return [{login: false ,tests: testsWithoutLogin}];
}
}
Note that if I write createRunner() part in runTests(), reporter is overwritten.

I think the problem here is that you use the same runner for parallel test execution in the Promise.race function. I would recommend you use different runners for both your test sets.

Related

Selenium 4.x execute "Page.addScriptToEvaluateOnNewDocument" properly

I'm having a strange issue where I really cannot find a solution. My production website needs some testing and of course bot checking is enabled (not naming it).
Under my tests after using "Page.addScriptToEvaluateOnNewDocument", the result is not what I would expect.
If I visit the website directly in the current tab of Chromium, it won't pass the bot checks.
If I manually open a new tab and visit the website, it passes the bot checks.
At first I thought the scripts are not being executed in the current tab, however checking things that I overwrite shows that they are.
Using this little script I've taken from puppeteer is a PASS in both tabs:
async function test() {
const results = {}
async function test(name, fn) {
const detectionPassed = await fn()
if (detectionPassed) {
console.log(`WARNING: Chrome headless detected via ${name}`)
} else {
console.log(`PASS: Chrome headless NOT detected via ${name}`)
}
results[name] = detectionPassed
}
await test('userAgent', _ => {
return /HeadlessChrome/.test(window.navigator.userAgent)
})
// Detects the --enable-automation || --headless flags
// Will return true in headful if --enable-automation is provided
await test('navigator.webdriver present', _ => {
return 'webdriver' in navigator
})
await test('window.chrome missing', _ => {
return /Chrome/.test(window.navigator.userAgent) && !window.chrome
})
await test('permissions API', async _ => {
const permissionStatus = await navigator.permissions.query({
name: 'notifications'
})
// eslint-disable-next-line
return (
Notification.permission === 'denied' && // eslint-disable-line no-undef
permissionStatus.state === 'prompt'
)
})
await test('permissions API overriden', _ => {
const permissions = window.navigator.permissions
if (permissions.query.toString() !== 'function query() { [native code] }')
return true
if (
permissions.query.toString.toString() !==
'function toString() { [native code] }'
)
return true
if (
permissions.query.toString.hasOwnProperty('[[Handler]]') && // eslint-disable-line no-prototype-builtins
permissions.query.toString.hasOwnProperty('[[Target]]') && // eslint-disable-line no-prototype-builtins
permissions.query.toString.hasOwnProperty('[[IsRevoked]]') // eslint-disable-line no-prototype-builtins
)
return true
if (permissions.hasOwnProperty('query')) return true // eslint-disable-line no-prototype-builtins
})
await test('navigator.plugins empty', _ => {
return navigator.plugins.length === 0
})
await test('navigator.languages blank', _ => {
return navigator.languages === ''
})
await test('iFrame for fresh window object', _ => {
// evaluateOnNewDocument scripts don't apply within [srcdoc] (or [sandbox]) iframes
// https://github.com/GoogleChrome/puppeteer/issues/1106#issuecomment-359313898
const iframe = document.createElement('iframe')
iframe.srcdoc = 'page intentionally left blank'
document.body.appendChild(iframe)
// Here we would need to rerun all tests with `iframe.contentWindow` as `window`
// Example:
return iframe.contentWindow.navigator.plugins.length === 0
})
// This detects that a devtools protocol agent is attached.
// So it will also pass true in headful Chrome if the devtools window is attached
await test('toString', _ => {
let gotYou = 0
const spooky = /./
spooky.toString = function() {
gotYou++
return 'spooky'
}
console.debug(spooky)
return gotYou > 1
})
return results
};
test();
My question would be, does "Page.addScriptToEvaluateOnNewDocument" really executes properly only in a new tab?
It really makes no sense to me why this is working in a new tab rather than in the current one.
Current setup and usage:
Chrome + Chrome Driver: v85.0.4183.83
Selenium Standalone: 4.1.1
Arguments:
--no-first-run --no-service-autorun --no-default-browser-check --disable-blink-features=AutomationControlled --window-size=1280,1024
excludeSwitches:
allow-pre-commit-input disable-background-networking
disable-backgrounding-occluded-windows
disable-client-side-phishing-detection disable-default-apps
disable-hang-monitor disable-popup-blocking disable-prompt-on-repost
disable-sync enable-automation enable-blink-features enable-logging
password-store test-type use-mock-keychain
Preferences:
profile.default_content_setting_values.popups 1
profile.default_content_setting_values.cookies 1
profile.cookie_controls_mode 0
UseAutomationExtension: false
Please advise, I would upmost appreciate it.
I have found out the solution. It's just how it works. Hurray.

TestCafe - Can You Pass ctx (Context) Variables to reporter?

I would like to know if I have a context variable like t.ctx.data, is there a way to get that to write the value of t.ctx.data to the TestCafe JSON reporter (or any reporter)?
My code:
// Called within Express.js by a request coming from req
const testMySite = (req, res) => {
process.env.PARAMS = JSON.stringify(req.body)
let testcafe = null;
console.log(`Running test on ports 1341 and 1342`)
createTestCafe('localhost', 1341, 1342, void 0, true)
.then(tc => {
testcafe = tc;
const runner = testcafe.createRunner()
return runner
.src(`${path.dirname(__filename)}/tests/gisTest.js`)
.browsers('firefox:headless')
.reporter('json', 'report.json')
.run()
})
.then(failedCount => {
testcafe.close()
})
res.json({message: `Success! Scraper has begun to process ${req.body}`});
}
My test code:
import { ClientFunction, Selector } from 'testcafe';
const doc = process.env.PARAMS
const newDoc = JSON.parse(process.env.PARAMS)
console.log(`newDoc (from test)`, newDoc)
// const _id = newDoc._id
let data = newDoc.mydata
fixture `My Fixture`
.page('https://www.mysite.co')
.afterEach(async t => {
await t
// how do I get t.ctx.myData into the reporter??
console.log(`t.ctx.myData: `, t.ctx.myData)
})
test(`My Test`, async t => {
const photoIcon = Selector('div#sbtc div.LM8x9c > span')
const photoFieldForPaste = Selector('input#Ycyxxc')
const searchByImageButton = Selector('td#aoghAf > input')
const targetElement = Selector('div#jHnbRc span:nth-child(2) > a')
await t
.wait(1000)
.click(photoIcon)
.typeText(photoFieldForPaste, data, {paste: true})
.click(searchByImageButton)
if(await targetElement.exists && await targetElement.visible) {
await t.ctx.finalData = targetElement.innerText;
}
await t.ctx.finalData = null;
})
Please see the part // how do I get t.ctx.myData into the reporter??.
I am assuming this is the only place where I could potentially get the data from the test into the reporter but I'm not sure exactly how.
If you know how to get the t.ctx.myData variable as shown in the above code to be written to the JSON reporter, I would highly appreciate it.
Even better would be to have a way to send the t.ctx.myData value into the response.
At present, you can add only static metadata to tests and fixtures. This metadata is available in reports. Please refer to the following article to get details: https://devexpress.github.io/testcafe/documentation/guides/basic-guides/organize-tests.html#specify-test-metadata
As for sending dynamic data to the reporter, we keep this feature in mind, however we cannot give any estimates on this. Please track the following issue: https://github.com/DevExpress/testcafe/issues/3584

Testcafe custom reporter doesn't return from this.formatError

I'm trying to implement a custom reporter for testcafe. If there is an error in testcase and this.formatError is called, it is not returning from this.formatError. Just hangs there indefinitely, I need to manually ctrl-c to stop the testcafe process. But if I remove this.formatError and try again it is working. What could be the reason?
Below is the code for reportTestDone()
reportTestDone (name, testRunInfo, meta) {
const errors = testRunInfo.errs;
const warnings = testRunInfo.warnings;
const hasErrors = !!errors.length;
const hasWarnings = !!warnings.length;
const result = hasErrors ? this.chalk.red('failed')
: this.chalk.green('passed');
const title = `${result} ${name} # ${meta.page}`;
this.write(title)
.newline();
if (hasErrors) {
this.write('Errors:')
.newline();
errors.forEach((error, idx) => {
this.setIndent(4)
.write(this.formatError(error, `${idx + 1} `)) //<-- this line is the problem
.write(error.errMsg)
.newline();
});
this.setIndent(0);
}
if (hasWarnings) {
this.newline()
.write('Warnings:');
warnings.forEach(warning => {
this.newline()
.write(warning);
});
}
this.setIndent(0);
}

Repeat Nightwatch test automatically

Does anyone know a way I can automatically rerun a Nightwatch test a set number of times?
I have the following code:
module.exports = {
'Log into system - create order': function (client) {
client
.url('XXXX')
.waitForElementVisible('body', 1000)
.assert.title('Reach - Log in')
.assert.visible('#UserName')
.setValue('#UserName', 'XXXX')
.assert.visible('#Password')
.setValue('#Password', 'XXXX')
.assert.visible('input[value="Login"]')
.click('input[value="Login"]')
.waitForElementVisible('img.test', 1000)
.assert.visible('li[title="XXXX"] a[tabindex="5"]')
.click('li[title="Sales"]')
.assert.cssClassPresent('li[title="XXXX"]', 'active')
.click('a[href="/Quotes/Add"]')
.waitForElementVisible('#s2id_CustomerId_Remote', 1000)
.click('#s2id_CustomerId_Remote')
.assert.visible('#s2id_autogen2_search')
.setValue('#s2id_autogen2_search', 'bik')
.waitForElementVisible('.select2-highlighted', 1000)
.click('.select2-highlighted')
.waitForElementVisible('#customerNotes', 1000)
.click('#s2id_ProductId_Remote')
.assert.visible('#s2id_autogen3_search')
.setValue('#s2id_autogen3_search', '123XP')
.pause(5000)
.assert.visible('.select2-highlighted')
.click('.select2-highlighted')
.pause(5000)
.assert.visible('.ui-sortable > tr')
.setValue('#Quote_PONumber', 'abc123')
.click('input[value="Create Order"]')
.waitForElementVisible('.ac-order-number', 1000)
.assert.visible('a[data-value="abc123"]')
.pause(5000)
.end()
}
}
rather than .end() the test I'd like to .rerun() the test say 30 times. I can't see an option to do this anywhere in the docs.
Many thanks in advance.
You can wrap your commands in a client.perform() and a for loop
client.perform(function(){
for (i = 0; i < 29; i++) {
client
.url('XXXX')
.
.
.
.end();
}
})
What you need is a little of async iteration logic and client.perform() function :
module.exports = {
'Log into system - create order': function (client) {
var currentIteration = 0,
iterationCount = 30;
function runTest() {
client
.url('XXXX')
// ... YOUR CODE HERE, WITHOUT .end()
.perform(function() {
if (++currentIteration < iterationCount) {
return runTest();
}
client.end(); // After passing 30 iterations end the session
});
}
runTest();
}
};
If you want to repeat he test with different inputs ? then you can do something like this
module.exports = {
"Login Fail Cases": function(browser) {
let dataSet = [
{ username: "madhus", pass: "madhus" },
{ username: "admin", pass: "admin" }
];
//will run for 2 times as length of dataset is 2
dataSet.forEach(function(data) {
browser
.url("https://localhost:3000/")
// you tests here
}, this);
// note: end the test outside the loop once all tests are executed
browser.end();
}
};

Import SQL dump within Node environment

I'd like a npm script to create/configure/etc. and finally import a SQL dump. The entire creation, configuring, etc. is all working, however, I cannot get the import to work. The data never is inserted. Here's what I have (nevermind the nested callback as they'll be turned into promises):
connection.query(`DROP DATABASE IF EXISTS ${config.database};`, err => {
connection.query(`CREATE DATABASE IF NOT EXISTS ${config.database};`, err => {
connection.query('use DATABASENAME', err => {
const sqlDumpPath = path.join(__dirname, 'sql-dump/sql-dump.sql');
connection.query(`SOURCE ${sqlDumpPath}`, err => {
connection.end(err => resolve());
});
})
});
});
I also tried the following with Sequelize (ORM):
return new Promise(resolve => {
const sqlDumpPath = path.join(__dirname, 'sql-dump/sql-dump.sql');
fs.readFile('./sql/dump.sql', 'utf-8', (err, data) => {
sequelize
.query(data)
.then(resolve)
.catch(console.error);
});
});
Here's how I set up my initial Sequelized import using the migrations framework. There is plenty of going on here but in short I:
find the latest sql-dump in the migrations folder
read the file using fs
split the text into queries
check if its a valid query and if so apply some cleaning that my data required (see related post)
push an array full of queries - I start with making sure that the database is clean by calling the this.down first
run everything as a promise (as suggested here) using the mapSeries (not the map)
Using sequelize-cli you can in your shell create a migration by writing:
sequelize migration:create
And you will automatically have the file where you enter the code below. In order to execute the migration you simply write:
sequelize db:migrate
"use strict";
const promise = require("bluebird");
const fs = require("fs");
const path = require("path");
const assert = require("assert");
const db = require("../api/models"); // To be able to run raw queries
const debug = require("debug")("my_new_api");
// I needed this in order to get some encoding issues straight
const Aring = new RegExp(String.fromCharCode(65533) +
"\\" + String.fromCharCode(46) + "{1,3}", "g");
const Auml = new RegExp(String.fromCharCode(65533) +
String.fromCharCode(44) + "{1,3}", "g");
const Ouml = new RegExp(String.fromCharCode(65533) +
String.fromCharCode(45) + "{1,3}", "g");
module.exports = {
up: function (queryInterface, Sequelize) {
// The following section allows me to have multiple sql-files and only use the last dump
var last_sql;
for (let fn of fs.readdirSync(__dirname)){
if (fn.match(/\.sql$/)){
fn = path.join(__dirname, fn);
var stats = fs.statSync(fn);
if (typeof last_sql === "undefined" ||
last_sql.stats.mtime < stats.mtime){
last_sql = {
filename: fn,
stats: stats
};
}
}
}
assert(typeof last_sql !== "undefined", "Could not find any valid sql files in " + __dirname);
// Split file into queries
var queries = fs.readFileSync(last_sql.filename).toString().split(/;\n/);
var actions = [{
query: "Running the down section",
exec: this.down
}]; // Clean database by calling the down first
for (let i in queries){
// Skip empty queries and the character set information in the 40101 section
// as this would most likely require a multi-query set-up
if (queries[i].trim().length == 0 ||
queries[i].match(new RegExp("/\\*!40101 .+ \\*/"))){
continue;
}
// The manual fixing of encoding
let clean_query = queries[i]
.replace(Aring, "Å")
.replace(Ouml, "Ö")
.replace(Auml, "Ä");
actions.push({
query: clean_query.substring(0, 200), // We save a short section of the query only for debugging purposes
exec: () => db.sequelize.query(clean_query)
});
}
// The Series is important as the order isn't retained with just map
return promise.mapSeries(actions, function(item) {
debug(item.query);
return item.exec();
}, { concurrency: 1 });
},
down: function (queryInterface, Sequelize) {
var tables_2_drop = [
"items",
"users",
"usertypes"
];
var actions = [];
for (let tbl of tables_2_drop){
actions.push({
// The created should be created_at
exec: () => db.sequelize.query("DROP TABLE IF EXISTS `" + tbl +"`")
});
}
return promise.map(actions, function(item) {
return item.exec();
}, { concurrency: 1 });/**/
}
};
Based loosely on Max Gordon's answer, here's my code to run a MySQL Dump file from NodeJs/Sequelize:
"use strict";
const fs = require("fs");
const path = require("path");
/**
* Start off with a MySQL Dump file, import that, and then migrate to the latest version.
*
* #param dbName {string} the name of the database
* #param mysqlDumpFile {string} The full path to the file to import as a starting point
*/
module.exports.migrateFromFile = function(dbName, mysqlDumpFile) {
let sequelize = createSequelize(dbName);
console.log("Importing from " + mysqlDumpFile + "...");
let queries = fs.readFileSync(mysqlDumpFile, {encoding: "UTF-8"}).split(";\n");
console.log("Importing dump file...");
// Setup the DB to import data in bulk.
let promise = sequelize.query("set FOREIGN_KEY_CHECKS=0"
).then(() => {
return sequelize.query("set UNIQUE_CHECKS=0");
}).then(() => {
return sequelize.query("set SQL_MODE='NO_AUTO_VALUE_ON_ZERO'");
}).then(() => {
return sequelize.query("set SQL_NOTES=0");
});
console.time("Importing mysql dump");
for (let query of queries) {
query = query.trim();
if (query.length !== 0 && !query.match(/\/\*/)) {
promise = promise.then(() => {
console.log("Executing: " + query.substring(0, 100));
return sequelize.query(query, {raw: true});
})
}
}
return promise.then(() => {
console.timeEnd("Importing mysql dump");
console.log("Migrating the rest of the way...");
console.time("Migrating after importing mysql dump");
return exports.migrateUp(dbName); // Run the rest of your migrations
}).then(() => {
console.timeEnd("Migrating after importing mysql dump");
});
};