Jest + Selenium: how do I do an operation before and after all tests inside a describe block is run? - selenium

I am trying to create a simple automated test to detect if the added element contains the text it is supposed to have. The test is run using node.js with jest command. I am using Selenium to automate the UI process and Jest to validate the UI's content.
I want to do the following.
Create variables that are accessible in all tests in the describe block before running any of the test
Close the Selenium-driven browser after all tests in the describe block is run
So far, I have this code.
const { Builder, By, until } = require('selenium-webdriver')
const url = 'http://127.0.0.1:3000'
describe('addUser', async() => {
afterAll(async() => {
await driver.quit()
}, 15000)
test('valid name and age should add a new element', async() => {
const driver = await new Builder().forBrowser('firefox').build()
await driver.get(url)
const nameField = await driver.wait(until.elementLocated(By.id('name')), 10000)
const ageField = await driver.wait(until.elementLocated(By.id('age')), 10000)
const btnAddUser = await driver.wait(until.elementLocated(By.id('btnAddUser')), 10000)
await nameField.click()
await nameField.sendKeys('Adam')
await ageField.click()
await ageField.sendKeys('39')
await btnAddUser.click()
const userItem = await driver.wait(until.elementLocated(By.css('.user-item')), 10000)
const userItemText = await userItem.getText()
expect(userItemText).toBe('Adam (39 years old)')
}, 10000)
})
The problems I am facing are the following.
I have to declare the driver, ask the driver to open a new page, and finding all the necessary elements every time I run a test. If possible, I would like to do these initialization steps inside a beforeAll function (by Jest) and store the variables somehow. Then, I can use driver, nameField, ageField, etc. in every test without having to declare them again. How would I do this while maintaining a clean code?
I will close the Selenium-driven browser after all tests inside the addUser describe block are run. So, I added driver.quit() inside afterAll (Jest) to close the browser. Unfortunately, this doesn't work; the browser doesn't close itself. How can I close the Selenium-operated browser after each describe block?
The test is working great, but how can I solve the two problems above?

driver variable is declared in test scope and is unavailable in afterAll. Even if it were declared in describe scope, a teardown would be performed only for the last driver because there can be multiple tests but afterAll is called after the last one.
Variables that need a teardown can be either redefined for each test:
let driver;
beforeEach(async () => {
driver = ...
});
afterEach(async () => {
await driver.quit()
});
Or reused for all tests:
let driver;
beforeAll(async () => {
driver = ...
});
afterAll(async () => {
await driver.quit()
});

Related

Testcafe data driven testing - how to drive tests with data fetched from API

I'm having trouble figuring out how to drive tests with data fetched from a request. I've read the documentation here: https://testcafe.io/documentation/402804/recipes/best-practices/create-data-driven-tests, and all examples use static json file data available at compile time.
I can't fetch the data in fixture.before hook, because it will only be available inside of the test context, but I need to access the data outside of the test context for iteration, such that the test is inside of a for loop.
I've tried this solution: https://github.com/DevExpress/testcafe/issues/1948, however this fails with testcafe ERROR No tests found in the specified source files. Ensure the sources contain the 'fixture' and 'test' directives., even when I use the flag disable-test-syntax-validation and .run({ disableTestSyntaxValidation: true }); option.
I am looking for suggestions and workarounds so that I can await some data, then run my tests. Even if Testcafe doesn't explicitly support something like this, I figure there must be some workaround... Thanks in advance.
Edit:
file-a.ts
export function tSteps(...args) {
// some setup
const testcase = args[args.length - 1];
const testCtx = test(name, async t => {
...
});
return testCtx;
}
----
file-b.ts
export const parameterizedTest = <T>(..., testcase: (scenario: T) => TestFn) => {
// some setup...
// I have also tried awaiting rows data here, which does not work
// because tests are not discoverable at compile time
...
const scenarios: T[] = rows.map(row => {
...
});
scenarios.forEach((scenario, idx) => {
return testcase(scenario).meta({
some metadata
});
});
};
----
tests.ts
fixture(...).before(async () => {
// can't get the data i need here because it needs to be available outside of the fixture context
})
parameterizedTest<MyInterface>(some params, (scenario: MyInterface) => {
return tSteps('my test',
async f => {
// some setup
// test code goes here which uses scenario.attributex, scenario.attributey, etc.
}
).meta(...);
}
);
In v1.0.0 and later, TestCafe does not validate test syntax. Please specify the TestCafe version that you use when you see the validation error.
Unfortunately, we cannot use pseudo-code to reproduce the issue you encountered. Please share some code that we could run to see the problematic behavior.
Generally speaking, TestCafe allows you to fetch data asynchronously and then spawn tests based on the received values. For instance, the following code works fine for me with TestCafe 1.18.3:
import { fixture, test } from 'testcafe';
import fetch from './node-fetch-mock';
(async () => {
const testData = await fetch();
testData
.map(n => () => {
fixture `Fixture ${n}`
.page `https://google.com`;
test(`Test ${n}`, async t => {
await t.expect(true).ok();
});
})
.map(async test => { await test(); });
})();
node-fetch-mock.js
export default async function fetch() {
return [1, 2, 3, 4, 5];
}
The only caveat is that I have to import fixture and test explicitly because I call them from callbacks.
Could you please provide us with any test code snippet that demonstrates the problem? We need to correctly understand the cause of the problem and reproduce it on our side.

Chromium/chrome running headless: wait for page load when using --screenshot?

Trying to take a screenshot using headless Chrome but it executes --screenshot before the page is loaded. On MacOS running zsh, I'm invoking it like this:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome https://example.com/foo.html --screenshot=/Users/myhome/foo.png
Is there a way to wait until the page is loaded before the screenshot is taken?
The answer, it turns out, is that those command line options don't work well and have been supplanted by Puppeteer. Here's the script I created (hardcodes values for brevity).
Here's the complete code.
const puppeteer = require('puppeteer');
const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
async function run () {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://twitter.com/gnuman1979/status/1239523796542992387');
await sleep(2000)
await page.screenshot({path: './foo.png'});
browser.close();
}
run();
Save as foo.js, then run using node:
$ node foo.js

Let Puppeteer wait for globalSetup to finish

I use Jest-Puppeteer to end2end test a webapplication. All tests run parallel with async functions. Now I find that the first test already runs before the globalSetup has finished and the data preperation is done (initializing client-settings etc.)
I've tried to timeout after the request, but that isn't working because now all requests have a timeout.
import puppeteer from "puppeteer";
import { getUrlByPath, post } from "../helper";
module.exports = async function globalSetup(globalConfig) {
await setupPuppeteer(globalConfig);
puppeteer.launch({args: ["--no-sandbox", "--disable-setuid-sandbox"]}).then(async browser => {
const page = await browser.newPage();
await post(
page,
getUrlByPath("somePath"),
"prepare_data_for_testing",
);
await browser.close();
});
};
Above code runs a globalConfig, after that it starts preparing the data for the testing environment.
Is there a way to make the test suites run AFTER this script returns the post with http 200: ok ?
I had to place await before puppeteer.launch and add require("expect-puppeteer"); at the top.

How to make testcafe wait until a fixture is done executing before moving to the next fixture when using concurrency?

I want to run testcafe tests concurrently BUT only executing against 1 file at a time.
In other words, I want to wait for all the tests of a specific fixture to be done executing before the tests from the next fixture start executing.
How do I do it?
You can do this using the TestCafe programming interface.
Please see the following example:
const createTestCafe = require('testcafe');
let testcafe = null;
let runner = null;
createTestCafe('localhost', 1337, 1338)
.then(tc => {
testcafe = tc;
runner = tc.createRunner()
.browsers('chrome')
.concurrency(3);
})
.then(() => {
return runner.src('fixture1.js').run();
})
.then(() => {
return runner.src('fixture2.js').run();
})
.then(() => {
testcafe.close();
});
However, please note that I run tests twice in sequence here. That means that your browsers will be opened twice too. You will also get two different reports.

TestCafe 'dynamic' tests cases

I created a few e2e sanity tests for my current project using TestCafe. These tests are standard TestCafe tests:
fixture(`Basic checkout flow`)
test('Main Flow', async (t) => {
});
I would like to execute this test for multiple site locales and for multiple channels. i.e. I need this test to run for nl_nl, nl_be, en_gb, .. and also for channels like b2c, b2b, ...
The easiest way is to create a loop in the test itself to loop over the locales and channels, but I want to run these test concurrently.
I tried to create a function to dynamically generate these tests, but TestCafe can't seem to detect the tests then.
dynamicTest('Main Flow', async (t) => {
});
function dynamicTest(testName, testFn) => {
const channels = ['b2c']
channels.forEach((channel) => {
test(`[Channel] ${channel}] ${testName}`, testFn);
});
};
Is there a better way of doing this? The only solution I see is running the test script multiple times from Jenkins to have concurrency.
more detailed code:
import HomePage from '../../page/HomePage/HomePage';
import EnvUtil from '../../util/EnvUtil';
const wrapper = (config, testFn) => {
config.locales.forEach(async locale =>
config.channels.forEach(async channel => {
const tstConfig = {
locale,
channel
};
tstConfig.env = EnvUtil.parse(tstConfig, config.args.env);
testConfig.foo = await EnvUtil.get() // If I remove this line it works!
testFn(config, locale, channel)
})
);
};
fixture(`[Feature] Feature 1`)
.beforeEach(async t => {
t.ctx.pages = {
home: new HomePage(),
... more pages here
};
});
wrapper(global.config, (testConfig, locale, channel) => {
test
.before(async (t) => {
t.ctx.config = testConfig;
})
.page(`foo.bar.com`)
(`[Feature] [Locale: ${locale.key}] [Channel: ${channel.key}] Feature 1`, async (t) => {
await t.ctx.pages.home.header.search(t, '3301');
.. more test code here
});
});
If I run it like this I get a "test is undefined" error. Is there something wrong in the way I'm wrapping "test"?
Using TestCafe of version 0.23.1, you can run tests imported from external libraries or generated dynamically even if the test file you provide does not contain any tests.
You can learn more here: Run Dynamically Loaded Tests