Skip to content

Commit

Permalink
smoke test converted to playwright (#4796)
Browse files Browse the repository at this point in the history
* smoke test converted to playwright

* add smoke test to nightly

* headless test

---------

Co-authored-by: gauravchawla <gaurav-chawla@hotmail.com>
  • Loading branch information
GauravChawlaMOJ and gauravchawla authored Oct 24, 2023
1 parent c1ad5b2 commit 9e06e6b
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 152 deletions.
29 changes: 16 additions & 13 deletions codecept.conf.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
require('./e2e/helpers/event_listener');
const lodash = require('lodash');

const config = {
WaitForTimeout: 5000,
WaitForAction: 350,
};

exports.config = {
output: './output',
multiple: {
Expand Down Expand Up @@ -39,19 +44,17 @@ exports.config = {
},
},
helpers: {
Puppeteer: {
show: process.env.SHOW_BROWSER_WINDOW || false,
restart: false,
keepCookies: true,
keepBrowserState: true,
waitForNavigation: ['networkidle2'],
waitForTimeout: parseInt(process.env.WAIT_FOR_TIMEOUT || '90000'),
chrome: {
ignoreHTTPSErrors: true,
args: process.env.DISABLE_WEB_SECURITY === 'true' ? [`--disable-web-security`,] : [],
devtools: false,
},
windowSize: '1280x960',
Playwright: {
url: 'http://localhost:3000',
show: false,
browser: 'chromium',
waitForTimeout: config.WaitForTimeout,
waitForAction: 350,
timeout: config.WaitForTimeout,
retries: 5,
waitForNavigation: 'load',
ignoreHTTPSErrors: true,
bypassCSP: true
},
HooksHelper: {
require: './e2e/helpers/hooks_helper.js',
Expand Down
41 changes: 24 additions & 17 deletions e2e/actors/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const { I } = inject();
module.exports = {
async signIn(user) {
console.log('base signIn');
if (!(this.isPuppeteer() && (currentUser === user))) {
if (!(currentUser === user)) {
console.log(`Logging in as ${user.email}`);
output.debug(`Logging in as ${user.email}`);
currentUser = {}; // reset in case the login fails
Expand Down Expand Up @@ -67,22 +67,16 @@ module.exports = {

async logWithHmctsAccount() {
const hmctsLoginIn = 'div.win-scroll';

if (await this.hasSelector(hmctsLoginIn)) {
if (!config.hmctsUser.email || !config.hmctsUser.password) {
throw new Error('For environment requiring hmcts authentication please provide HMCTS_USER_USERNAME and HMCTS_USER_PASSWORD environment variables');
}
await within(hmctsLoginIn, () => {
this.fillField('//input[@type="email"]', config.hmctsUser.email);
this.wait(0.2);
this.click('Next');
this.wait(0.2);
this.fillField('//input[@type="password"]', config.hmctsUser.password);
this.wait(0.2);
this.click('Sign in');
this.click('Yes');
});
if (!config.hmctsUser.email || !config.hmctsUser.password) {
throw new Error('For environment requiring hmcts authentication please provide HMCTS_USER_USERNAME and HMCTS_USER_PASSWORD environment variables');
}
await within(hmctsLoginIn, () => {
this.fillField('//input[@type="text"]', config.hmctsUser.email);
this.wait(0.2);
this.fillField('//input[@type="password"]', config.hmctsUser.password);
this.wait(0.2);
this.click('Sign in');
});
},

async rejectCookies() {
Expand All @@ -106,6 +100,19 @@ module.exports = {
return caseId;
},

async createCaseSmokeTest(user, caseName, outsourcingLA) {
await this.waitForSelector('ccd-search-result');
await this.waitForSelector('a[href="/cases/case-filter"]');
await this.retryUntilExists(() => this.click('a[href="/cases/case-filter"]'), openApplicationEventPage.fields.jurisdiction, true, 10);

await openApplicationEventPage.populateForm(caseName, outsourcingLA);
I.click('Submit');
this.waitForElement('.alert-message', 90);
const caseId = normalizeCaseId(await this.grabTextFrom('.alert-message'));
output.print(`Case created #${caseId}`);
return caseId;
},

async completeEvent(button, changeDetails, confirmationPage = false, selector = '.mat-tab-list') {
await this.retryUntilExists(() => this.click('submit'), '.check-your-answers');
if (changeDetails != null) {
Expand Down Expand Up @@ -378,7 +385,7 @@ module.exports = {
async retryUntilExists(action, locator, checkUrlChanged = true, maxNumberOfTries = maxRetries) {
const originalUrl = await this.grabCurrentUrl();
// override this for now
maxNumberOfTries = 1;
//maxNumberOfTries = 1;

for (let tryNumber = 1; tryNumber <= maxNumberOfTries; tryNumber++) {
output.log(`retryUntilExists(${locator}): starting try #${tryNumber}`);
Expand Down
4 changes: 2 additions & 2 deletions e2e/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ module.exports = {
password: process.env.SMOKE_TEST_LA_USER_PASSWORD || defaultPassword,
},
hmctsUser: {
email: process.env.HMCTS_USER_USERNAME,
password: process.env.HMCTS_USER_PASSWORD,
email: process.env.HMCTS_USER_USERNAME || 'kurt@swansea.gov.uk',
password: process.env.HMCTS_USER_PASSWORD || defaultPassword,
},
privateSolicitorOne: {
email: 'solicitor1@solicitors.uk',
Expand Down
147 changes: 31 additions & 116 deletions e2e/helpers/browser_helper.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,28 @@
const testConfig = require('../config.js');
const {runAccessibility} = require('./accessibility/runner');
const { runAccessibility } = require('./accessibility/runner');
const { Helper } = require('codeceptjs');

module.exports = class BrowserHelpers extends Helper {

module.exports = class BrowserHelpers extends Helper {
getHelper() {
return this.helpers['Puppeteer'] || this.helpers['WebDriver'];
}

isPuppeteer() {
return this.helpers['Puppeteer'];
return this.helpers['Playwright']; // Use Playwright instead of Puppeteer or WebDriver
}

async getBrowser() {
const helper = this.getHelper();
if (this.isPuppeteer()) {
return (await helper.options.browser);
}
return (await helper.config.browser);
return helper.page; // Use helper.page for Playwright
}

clickBrowserBack() {
async clickBrowserBack() {
const helper = this.getHelper();
if (this.isPuppeteer()) {
return helper.page.goBack();
} else {
return helper.browser.back();
}
await helper.page.goBack(); // Use page.goBack() for Playwright
}

reloadPage() {
async reloadPage() {
const helper = this.getHelper();
return helper.refreshPage();
await helper.page.reload(); // Use page.reload() for Playwright
}

/**
* Finds elements described by selector.
* If element cannot be found an empty collection is returned.
*
* @param selector - element selector
* @returns {Promise<Array>} - promise holding either collection of elements or empty collection if element is not found
*/
async locateSelector(selector) {
const helper = this.getHelper();
return helper._locate(selector);
Expand All @@ -49,101 +32,50 @@ module.exports = class BrowserHelpers extends Helper {
return (await this.locateSelector(selector)).length;
}

/**
* Finds element described by locator.
* If element cannot be found immediately function retries ever `retryInterval` seconds until `sec` seconds is reached.
* If element still cannot be found after the waiting time an undefined is returned.
*
* @param locator - element CSS locator
* @param timeout - optional time in seconds to wait
* @returns {Promise<undefined|*>} - promise holding either an element or undefined if element is not found
*/
async waitForSelector(locator, timeout = 60) {
async waitForSelector(locator, timeout = 30) {
const helper = this.getHelper();

const retryInterval = 5;
const numberOfRetries = timeout < retryInterval ? 1 : Math.floor(timeout / retryInterval);
// console.log('timeout: ' + timeout);
// console.log('retryInterval: ' + retryInterval);
// console.log('timeout / retryInterval: ' + (timeout / retryInterval));
// console.log('Math.floor(timeout / retryInterval): ' + Math.floor(timeout / retryInterval));
// console.log('numberOfRetries: ' + numberOfRetries);

let result = undefined;
for (let tryNumber = 0; tryNumber <= numberOfRetries; tryNumber++) {
console.log('waitForSelector ' + locator + ' try number ' + (tryNumber+1));
console.log('waitForSelector ' + locator + ' try number ' + (tryNumber + 1));
try {
if (this.isPuppeteer()) {
const context = await helper._getContext();
result = await context.waitForSelector(locator, {timeout: retryInterval * 1000});
} else {
result = await helper.waitForElement(locator, retryInterval);
}
await helper.page.waitForSelector(locator, { timeout: retryInterval * 1000 });
} catch (e) {
if (e.name !== 'TimeoutError') {
throw e;
}
}
if (result !== undefined) {
console.log('found it!');
return result;
}
console.log('found it!');
return;
}
console.log('not found it after ' + (numberOfRetries + 1) + ' tries (' + timeout + 's)');
return result;
}

async waitForAnySelector(selectors, maxWaitInSecond) {
return this.waitForSelector([].concat(selectors).join(','), maxWaitInSecond);
return this.waitForSelector(selectors.join(','), maxWaitInSecond);
}

async canSee(selector) {
const helper = this.getHelper();
try {
const numVisible = await helper.grabNumberOfVisibleElements(selector);
return !!numVisible;
const numVisible = await helper.page.locator(selector).count();
return numVisible > 0;
} catch (err) {
return false;
}
}

/**
* Grabs text from a element specified by locator.
*
* Note: When error is not found undefined is returned. That behaviour is bit different from a behaviour of
* default `grabTextFrom` function which throws an error and fails test when element is not found.
*
* @param locator - element locator
* @returns {Promise<string|undefined>}
*/
async grabText(locator) {
const elements = await this.locateSelector(locator);

const texts = elements.map(async (element) => {
return (await element.getProperty('innerText')).jsonValue();
});

return texts[0];
const helper = this.getHelper();
const element = await helper.page.locator(locator).first();
return element ? element.innerText() : undefined;
}

async grabAttribute(locator, attr) {
const elements = await this.locateSelector(locator);

let getAttribute;

if(this.isPuppeteer()){
getAttribute = async (element, attr) => (await element.getProperty(attr)).jsonValue();
} else {
getAttribute = async (element, attr) => await element.getAttribute(attr);
}

const texts = elements.map(async element => getAttribute(element, attr));

if (texts.length > 1) {
throw new Error(`More than one element found for locator ${locator}`);
} else {
return texts[0];
}
const helper = this.getHelper();
const element = await helper.page.locator(locator).first();
return element ? element.getAttribute(attr) : undefined;
}

async runAccessibilityTest() {
Expand All @@ -152,41 +84,24 @@ module.exports = class BrowserHelpers extends Helper {
if (!testConfig.TestForAccessibility) {
return;
}
const url = await helper.grabCurrentUrl();
const {page} = await helper;
const url = await helper.page.url();
const page = helper.page;

await runAccessibility(url, page);
}

async canClick(selector){
async canClick(selector) {
const elements = await this.locateSelector(selector);

if (elements.length > 1) {
throw new Error(`More than one element found for locator ${selector}`);
}
if(elements.length === 0){
throw new Error(`No element found for locator ${selector}`);
}
else if(elements[0].isClickable) {
return await elements[0].isClickable();
}
return true;
return elements.length > 0 && (await elements[0].isIntersectingViewport());
}

async scrollToElement(selector) {
const helper = this.getHelper();
const elements = await this.locateSelector(selector);

if (elements.length > 1) {
throw new Error(`More than one element found for locator ${selector}`);
}
if(elements.length === 0){
throw new Error(`No element found for locator ${selector}`);
}
if(this.isPuppeteer()){
await helper.page.evaluate(selectorArg => document.querySelector(selectorArg).scrollIntoView(), selector);
const element = await helper.page.locator(selector).first();
if (element) {
await element.scrollIntoViewIfNeeded();
} else {
await helper.executeScript('arguments[0].scrollIntoView()', elements[0]);
throw new Error(`No element found for locator ${selector}`);
}
}
};
3 changes: 2 additions & 1 deletion e2e/pages/caseList.page.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ module.exports = {
// wait for our filters to load
I.waitForVisible(this.fields.caseName, 30);
I.fillField(this.fields.caseName, caseName);
I.click(this.fields.search);
I.wait(30);
I.click('Apply');
I.runAccessibilityTest().then(() => {});
},

Expand Down
4 changes: 2 additions & 2 deletions e2e/tests/smoke_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ Scenario('Sign in as local authority and create a case', async ({I, caseListPage
output.print('Smoke test triggered');
await I.goToPage(config.baseUrl);
const caseName = `Smoke test case (${moment().format('YYYY-MM-DD HH:MM')})`;
const caseId = await I.logInAndCreateCase(config.swanseaLocalAuthorityUserOne, caseName);
const caseId = await I.createCaseSmokeTest(config.swanseaLocalAuthorityUserOne, caseName);
I.navigateToCaseList();
I.grabCurrentUrl();
caseListPage.searchForCasesWithName(caseName, 'Open');
I.grabCurrentUrl();
I.waitForElement(`//ccd-search-result/table/tbody//tr//td//a[contains(@href,'/cases/case-details/${caseId}')]`, 60);
I.waitForElement(`//ccd-search-result/table/tbody//tr//td//a[contains(@href,'/cases/case-details/${caseId}')]`, 90);
I.grabCurrentUrl();
I.seeCaseInSearchResult(caseId);
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"test": "codeceptjs run-multiple parallel",
"test:functional": "NODE_TLS_REJECT_UNAUTHORIZED=0 MOCHAWESOME_REPORTFILENAME=functional codeceptjs run-multiple parallel --grep '(?=.*)^(?!.*(@smoke-tests|@flaky|@nightlyOnly))' --reporter mocha-multi --verbose",
"test:functional-nightly1": "NODE_TLS_REJECT_UNAUTHORIZED=0 MOCHAWESOME_REPORTFILENAME=functional codeceptjs run-multiple parallel --grep '(?=.*)^(?!.*(@smoke-tests))' --reporter mocha-multi --verbose",
"test:functional-nightly": "NODE_TLS_REJECT_UNAUTHORIZED=0 MOCHAWESOME_REPORTFILENAME=functional codeceptjs run e2e/tests/adminManagesOrders_test.js --reporter mocha-multi --verbose",
"test:functional-nightly": "NODE_TLS_REJECT_UNAUTHORIZED=0 MOCHAWESOME_REPORTFILENAME=functional codeceptjs run e2e/tests/smoke_test.js --reporter mocha-multi --verbose",
"test:smoke": "MOCHAWESOME_REPORTFILENAME=smoke REPORT_DIR=test-results/smokeTest REPORT_FILE=test-results/smokeTest/results.xml codeceptjs run --grep '@smoke-tests' --reporter mocha-multi --verbose",
"test:crossbrowser-e2e": "NODE_TLS_REJECT_UNAUTHORIZED=0 MOCHAWESOME_REPORTFILENAME=crossbrowser codeceptjs run-multiple --grep '@skip' ${BROWSER_GROUP:-'--all'} -c saucelabs.conf.js --reporter mocha-multi --verbose",
"test:crossbrowser": "./bin/run-crossbrowser-tests.sh",
Expand Down

0 comments on commit 9e06e6b

Please sign in to comment.