├── .gitignore
├── LICENSE
├── README.md
├── features
├── github.feature
├── google.feature
├── step_definitions
│ ├── github.js
│ └── google.js
└── support
│ ├── errorHandling.js
│ ├── hooks.js
│ ├── pages
│ └── github-page.js
│ ├── testControllerHolder.js
│ └── world.js
├── package.json
└── reports
└── .gitkeep
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependency directory
2 | node_modules
3 |
4 | # Screenshots
5 | reports/screenshots
6 |
7 | # Added empty reports folder
8 | reports/*
9 | !reports/.gitkeep
10 |
11 | #VSCode files
12 | .vscode/*
13 |
14 | #Linter
15 | .eslintrc.json
16 |
17 | #Package-lock
18 | package-lock.json
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Ryan Quellhorst
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Integration of TestCafe and CucumberJS
2 |
3 | This is a demonstration of integration [TestCafé](https://github.com/DevExpress/testcafe) into [CucumberJS](https://github.com/cucumber/cucumber-js) tests using TestCafe and Cucumber.
4 |
5 | Big thank you to [helen-dikareva](https://github.com/helen-dikareva/) for your help in starting the integration with your [repo](https://github.com/helen-dikareva/testcafe-cucumber-demo). This is a fork of all of the hard work you've put in.
6 |
7 | Also, thanks to the team at [TestCafé](https://github.com/DevExpress/testcafe) for allowing testers to break away from Selenium.
8 |
9 | **Depreciation Notice** - [There are talks to officially support the Gherkin syntax in TestCafé](https://github.com/DevExpress/testcafe/issues/1373#issuecomment-291526857). Once those changes are in place I will no longer support this repo. Please voice your support of these changes becoming native to TestCafé.
10 |
11 | ## Versions
12 |
13 |
14 | TestCafé |
15 | 1.1.0 |
16 |
17 |
18 | CucumberJS |
19 | 5.1.0 |
20 |
21 |
22 |
23 | ## Installation
24 |
25 | 1. Make sure [Node.js](https://nodejs.org/) is installed
26 | 2. Navigate to the root of the repo
27 | 3. Use the `npm install` command
28 |
29 | ## Running tests
30 |
31 | ### Windows
32 | You can run tests by executing the `.\node_modules\.bin\cucumber-js.cmd` or `npm test` commands in command prompt
33 |
34 | ### Mac or Linux
35 | You can run tests by executing `node_modules/cucumber/bin/cucumber-js`
36 |
37 | ## Documentation
38 | * [Initial Setup](https://github.com/rquellh/testcafe-cucumber/wiki/Initial-Setup)
39 | * [Debuging in VSCode](https://github.com/rquellh/testcafe-cucumber/wiki/Debugging-in-VSCode)
40 | * [Using TestCafé](https://github.com/rquellh/testcafe-cucumber/wiki/Using-TestCafe)
41 | * [Creating your first test](https://github.com/rquellh/testcafe-cucumber/wiki/Creating-your-first-test)
42 | * [Selectors](https://github.com/rquellh/testcafe-cucumber/wiki/Selectors)
43 | * [Actions](https://github.com/rquellh/testcafe-cucumber/wiki/Actions)
44 | * [Assertions](https://github.com/rquellh/testcafe-cucumber/wiki/Assertions)
45 | * [TestCafé & CucumberJS](https://github.com/rquellh/testcafe-cucumber/wiki/TestCafe-&-CucumberJS)
46 | * [Helpful VSCode Setup](https://github.com/rquellh/testcafe-cucumber/wiki/Helpful-VSCode-Setup)
47 | * [Creating Features](https://github.com/rquellh/testcafe-cucumber/wiki/Creating-Features)
48 | * [Creating Step Definitions](https://github.com/rquellh/testcafe-cucumber/wiki/Creating-Step-Definitions)
49 | * [Adding TestCafé Functionality to Cucumber steps](https://github.com/rquellh/testcafe-cucumber/wiki/Adding-TestCafe-Functionality-to-Cucumber-steps)
50 | * [Harnessing Cucumber's Power](https://github.com/rquellh/testcafe-cucumber/wiki/Harnessing-Cucumber's-Power)
51 | * [Page Object](https://github.com/rquellh/testcafe-cucumber/wiki/Page-Object)
52 | * [Running Tests](https://github.com/rquellh/testcafe-cucumber/wiki/Running-Tests)
53 | * [Reporting and Taking Screenshots](https://github.com/rquellh/testcafe-cucumber/wiki/Reporting-and-Taking-Screenshots)
54 |
55 | ## Notes
56 |
57 | * As of the time I am writting this, there is only 1 passing test of 3. I decided to not make all of the tests passing, so you could see how failures are handled.
58 |
59 | * My solution closes the TestCafé browser between each scenario. I tried to keep it open between scenarios but had trouble with handling failures. If you find a solution, I'd like to know.
60 |
61 | * With TestCafé version 0.19.0, you no longer have to manually update stack-chain. Thank you to the TestCafé crew for making the integration much easier.
62 |
63 | ## Contributors
64 | Thanks to everyone who has contributed to this project over the last few years.
65 |
66 | [
](https://github.com/cmasekar) |[
](https://github.com/benkirbyten10) |[
](https://github.com/vvedachalam) |[
](https://github.com/azzra) |
67 | :---: |:---: |:---: |:---: |
68 |
--------------------------------------------------------------------------------
/features/github.feature:
--------------------------------------------------------------------------------
1 | Feature: Searching for TestCafe on GitHub
2 |
3 | I want to find TestCafe repository on GitHub
4 |
5 | Scenario: Searching for TestCafe on GitHub
6 | Given I open the GitHub page
7 | When I am typing my search request "TestCafe" on GitHub
8 | Then I am pressing enter key on GitHub
9 | Then I should see that the first GitHub's result is DevExpress/testcafe
10 |
11 | Scenario: Try to use TestCafe Role
12 | Given I open the GitHub page
13 | Then I am trying to use Role
14 |
--------------------------------------------------------------------------------
/features/google.feature:
--------------------------------------------------------------------------------
1 | Feature: Searching for TestCafe by Google
2 |
3 | I want to find TestCafe repository by Google search
4 |
5 | Scenario: Searching for TestCafe by Google
6 | Given I am open Google's search page
7 | When I am typing my search request "github TestCafe" on Google
8 | Then I press the "enter" key on Google
9 | Then I should see that the first Google's result is "GitHub - DevExpress/testcafe:"
10 |
11 |
12 | Scenario: Failing scenario
13 | Given I am open Google's search page
14 | When I am typing my search request "github TestCafe" on Google
15 | Then I press the "enter" key on Google
16 | Then I should see that the first Google's result is "kittens"
--------------------------------------------------------------------------------
/features/step_definitions/github.js:
--------------------------------------------------------------------------------
1 | const {Given, When, Then} = require('cucumber');
2 | const Role = require('testcafe').Role;
3 | const githubPage = require('../support/pages/github-page');
4 |
5 | Given(/^I open the GitHub page$/, async function() {
6 | await testController.navigateTo(githubPage.github.url());
7 | });
8 |
9 | When(/^I am typing my search request "([^"]*)" on GitHub$/, async function(text) {
10 | await testController.typeText(githubPage.github.searchButton(), text);
11 | });
12 |
13 | Then(/^I am pressing (.*) key on GitHub$/, async function(text) {
14 | await testController.pressKey(text);
15 | });
16 |
17 | Then(/^I should see that the first GitHub\'s result is (.*)$/, async function(text) {
18 | await testController.expect(githubPage.github.firstSearchResult().innerText).contains(text);
19 | });
20 |
21 | const gitHubRoleForExample = Role(githubPage.github.url() + 'login', async function(t) {
22 | await t
23 | .click(githubPage.github.loginButton())
24 | .expect(githubPage.github.loginErrorMessage().innerText).contains('Incorrect username or password.');
25 | });
26 |
27 | Then(/^I am trying to use (.*)$/, async function(text) {
28 | await testController.useRole(gitHubRoleForExample);
29 | });
30 |
--------------------------------------------------------------------------------
/features/step_definitions/google.js:
--------------------------------------------------------------------------------
1 | const {Given, When, Then} = require('cucumber');
2 | const Selector = require('testcafe').Selector;
3 |
4 | Given('I am open Google\'s search page', async function() {
5 | await testController.navigateTo('https://google.com');
6 | });
7 |
8 | When('I am typing my search request {string} on Google', async function(text) {
9 | var input = Selector('.gLFyf').with({boundTestRun: testController});
10 | await this.addScreenshotToReport();
11 | await testController.typeText(input, text);
12 | });
13 |
14 | Then('I press the {string} key on Google', async function(text) {
15 | await testController.pressKey(text);
16 | });
17 |
18 | Then('I should see that the first Google\'s result is {string}', async function(text) {
19 | var firstLink = Selector('#rso').find('a').with({boundTestRun: testController});
20 | await testController.expect(firstLink.innerText).contains(text);
21 | });
22 |
--------------------------------------------------------------------------------
/features/support/errorHandling.js:
--------------------------------------------------------------------------------
1 | const testcafe = require('testcafe');
2 | const hooks = require('../support/hooks');
3 |
4 | exports.addErrorToController = function() {
5 | testController.executionChain
6 | .catch(function(result) {
7 | const errAdapter = new testcafe.embeddingUtils.TestRunErrorFormattableAdapter(result, {
8 | testRunPhase: testController.testRun.phase,
9 | userAgent: testController.testRun.browserConnection.browserInfo.userAgent,
10 | });
11 | return testController.testRun.errs.push(errAdapter);
12 | });
13 | };
14 |
15 | exports.ifErrorTakeScreenshot = function(resolvedTestController) {
16 |
17 | if (hooks.getIsTestCafeError() === true && testController.testRun.opts.takeScreenshotsOnFails === true) {
18 | if (process.argv.includes('--format') || process.argv.includes('-f') || process.argv.includes('--format-options')) {
19 | resolvedTestController.executionChain._state = "fulfilled"
20 | return resolvedTestController.takeScreenshot().then(function(path) {
21 | return hooks.getAttachScreenshotToReport(path);
22 | });
23 | } else {
24 | return resolvedTestController.takeScreenshot();
25 | }
26 | }
27 | };
28 |
--------------------------------------------------------------------------------
/features/support/hooks.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const createTestCafe = require('testcafe');
3 | const testControllerHolder = require('../support/testControllerHolder');
4 | const {AfterAll, setDefaultTimeout, Before, After, Status} = require('cucumber');
5 | const errorHandling = require('../support/errorHandling');
6 | const TIMEOUT = 20000;
7 |
8 | let isTestCafeError = false;
9 | let attachScreenshotToReport = null;
10 | let cafeRunner = null;
11 | let n = 0;
12 |
13 | function createTestFile() {
14 | fs.writeFileSync('test.js',
15 | 'import errorHandling from "./features/support/errorHandling.js";\n' +
16 | 'import testControllerHolder from "./features/support/testControllerHolder.js";\n\n' +
17 |
18 | 'fixture("fixture")\n' +
19 |
20 | 'test\n' +
21 | '("test", testControllerHolder.capture)')
22 | }
23 |
24 | function runTest(iteration, browser) {
25 | createTestCafe('localhost', 1338 + iteration, 1339 + iteration)
26 | .then(function(tc) {
27 | cafeRunner = tc;
28 | const runner = tc.createRunner();
29 | return runner
30 | .src('./test.js')
31 | .screenshots('reports/screenshots/', true)
32 | .browsers(browser)
33 | .run()
34 | .catch(function(error) {
35 | console.error(error);
36 | });
37 | })
38 | .then(function(report) {
39 | });
40 | }
41 |
42 |
43 | setDefaultTimeout(TIMEOUT);
44 |
45 | Before(function() {
46 | runTest(n, this.setBrowser());
47 | createTestFile();
48 | n += 2;
49 | return this.waitForTestController.then(function(testController) {
50 | return testController.maximizeWindow();
51 | });
52 | });
53 |
54 | After(function() {
55 | fs.unlinkSync('test.js');
56 | testControllerHolder.free();
57 | });
58 |
59 | After(async function(testCase) {
60 | const world = this;
61 | if (testCase.result.status === Status.FAILED) {
62 | isTestCafeError = true;
63 | attachScreenshotToReport = world.attachScreenshotToReport;
64 | errorHandling.addErrorToController();
65 | await errorHandling.ifErrorTakeScreenshot(testController)
66 | }
67 | });
68 |
69 | AfterAll(function() {
70 | let intervalId = null;
71 |
72 | function waitForTestCafe() {
73 | intervalId = setInterval(checkLastResponse, 500);
74 | }
75 |
76 | function checkLastResponse() {
77 | if (testController.testRun.lastDriverStatusResponse === 'test-done-confirmation') {
78 | cafeRunner.close();
79 | process.exit();
80 | clearInterval(intervalId);
81 | }
82 | }
83 |
84 | waitForTestCafe();
85 | });
86 |
87 | const getIsTestCafeError = function() {
88 | return isTestCafeError;
89 | };
90 |
91 | const getAttachScreenshotToReport = function(path) {
92 | return attachScreenshotToReport(path);
93 | };
94 |
95 | exports.getIsTestCafeError = getIsTestCafeError;
96 | exports.getAttachScreenshotToReport = getAttachScreenshotToReport;
97 |
--------------------------------------------------------------------------------
/features/support/pages/github-page.js:
--------------------------------------------------------------------------------
1 | const {Selector} = require('testcafe');
2 |
3 | // Selectors
4 |
5 | function select(selector) {
6 | return Selector(selector).with({boundTestRun: testController});
7 | }
8 |
9 | exports.github = {
10 | url: function() {
11 | return 'https://github.com/';
12 | },
13 | searchBox: function() {
14 | return select('.header-search-input');
15 | },
16 | firstSearchResult: function() {
17 | return Selector('.repo-list-item').nth(0).with({boundTestRun: testController});
18 | },
19 | loginButton: function() {
20 | return select('.btn.btn-primary.btn-block');
21 | },
22 | loginErrorMessage: function() {
23 | return select('#js-flash-container > div > div');
24 | },
25 | searchButton: function() {
26 | return select('.header-search-input');
27 | },
28 | firstSearchResult: function() {
29 | return Selector('.repo-list-item').nth(0).with({boundTestRun: testController});
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/features/support/testControllerHolder.js:
--------------------------------------------------------------------------------
1 | const testControllerHolder = {
2 |
3 | testController: null,
4 | captureResolver: null,
5 | getResolver: null,
6 |
7 | capture: function(t) {
8 | testControllerHolder.testController = t;
9 |
10 | if (testControllerHolder.getResolver) {
11 | testControllerHolder.getResolver(t);
12 | }
13 |
14 | return new Promise(function(resolve) {
15 | testControllerHolder.captureResolver = resolve;
16 | });
17 | },
18 |
19 | free: function() {
20 | testControllerHolder.testController = null;
21 |
22 | if (testControllerHolder.captureResolver) {
23 | testControllerHolder.captureResolver();
24 | }
25 | },
26 |
27 | get: function() {
28 | return new Promise(function(resolve) {
29 | if (testControllerHolder.testController) {
30 | resolve(testControllerHolder.testController);
31 | } else {
32 | testControllerHolder.getResolver = resolve;
33 | }
34 | });
35 | },
36 | };
37 |
38 | module.exports = testControllerHolder;
39 |
--------------------------------------------------------------------------------
/features/support/world.js:
--------------------------------------------------------------------------------
1 | const {setWorldConstructor} = require('cucumber');
2 | const testControllerHolder = require('./testControllerHolder');
3 | const base64Img = require('base64-img');
4 |
5 | function CustomWorld({attach, parameters}) {
6 |
7 | this.waitForTestController = testControllerHolder.get()
8 | .then(function(tc) {
9 | return testController = tc;
10 | });
11 |
12 | this.attach = attach;
13 |
14 | this.setBrowser = function() {
15 | if (parameters.browser === undefined) {
16 | return 'chrome';
17 | } else {
18 | return parameters.browser;
19 | }
20 | };
21 |
22 | this.addScreenshotToReport = function() {
23 | if (process.argv.includes('--format') || process.argv.includes('-f') || process.argv.includes('--format-options')) {
24 | testController.takeScreenshot()
25 | .then(function(screenshotPath) {
26 | const imgInBase64 = base64Img.base64Sync(screenshotPath);
27 | const imageConvertForCuc = imgInBase64.substring(imgInBase64.indexOf(',') + 1);
28 | return attach(imageConvertForCuc, 'image/png');
29 | })
30 | .catch(function(error) {
31 | console.warn('The screenshot was not attached to the report');
32 | });
33 | } else {
34 | return new Promise((resolve) => {
35 | resolve(null);
36 | });
37 | }
38 | };
39 |
40 | this.attachScreenshotToReport = function(pathToScreenshot) {
41 | const imgInBase64 = base64Img.base64Sync(pathToScreenshot);
42 | const imageConvertForCuc = imgInBase64.substring(imgInBase64.indexOf(',') + 1);
43 | return attach(imageConvertForCuc, 'image/png');
44 | };
45 | }
46 |
47 | setWorldConstructor(CustomWorld);
48 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "testcafe-cucumber",
3 | "version": "0.1.0",
4 | "description": "An integration of TestCafe and CucumberJS",
5 | "author": "Ryan Quellhorst ",
6 | "contributors": [
7 | "Chirag Masekar"
8 | ],
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/rquellh/testcafe-cucumber"
13 | },
14 | "scripts": {
15 | "debug": "node --inspect=1337 --debug-brk --nolazy node_modules/cucumber/bin/cucumber-js --tags @debug --format json:./reports/report.json",
16 | "test": "./node_modules/.bin/cucumber-js.cmd",
17 | "test-report": "./node_modules/.bin/cucumber-js.cmd --format json:./reports/report.json",
18 | "test-chrome": "./node_modules/.bin/cucumber-js.cmd --world-parameters \"{\\\"browser\\\": \\\"chrome\\\"}\"",
19 | "test-ie": "./node_modules/.bin/cucumber-js.cmd --world-parameters \"{\\\"browser\\\": \\\"ie\\\"}\"",
20 | "test-edge": "./node_modules/.bin/cucumber-js.cmd --world-parameters \"{\\\"browser\\\": \\\"edge\\\"}\"",
21 | "test-firefox": "./node_modules/.bin/cucumber-js.cmd --world-parameters \"{\\\"browser\\\": \\\"firefox\\\"}\"",
22 | "test-opera": "./node_modules/.bin/cucumber-js.cmd --world-parameters \"{\\\"browser\\\": \\\"opera\\\"}\"",
23 | "test-safari": "./node_modules/.bin/cucumber-js.cmd --world-parameters \"{\\\"browser\\\": \\\"safari\\\"}\"",
24 | "test-chrome-report": "./node_modules/.bin/cucumber-js.cmd --format json:./reports/report.json --world-parameters \"{\\\"browser\\\": \\\"chrome\\\"}\"",
25 | "test-ie-report": "./node_modules/.bin/cucumber-js.cmd --format json:./reports/report.json --world-parameters \"{\\\"browser\\\": \\\"ie\\\"}\"",
26 | "test-edge-report": "./node_modules/.bin/cucumber-js.cmd --format json:./reports/report.json --world-parameters \"{\\\"browser\\\": \\\"edge\\\"}\"",
27 | "test-firefox-report": "./node_modules/.bin/cucumber-js.cmd --format json:./reports/report.json --world-parameters \"{\\\"browser\\\": \\\"firefox\\\"}\"",
28 | "test-opera-report": "./node_modules/.bin/cucumber-js.cmd --format json:./reports/report.json --world-parameters \"{\\\"browser\\\": \\\"opera\\\"}\"",
29 | "test-safari-report": "./node_modules/.bin/cucumber-js.cmd --format json:./reports/report.json --world-parameters \"{\\\"browser\\\": \\\"safari\\\"}\"",
30 | "test-chrome-headless": "./node_modules/.bin/cucumber-js.cmd --world-parameters \"{\\\"browser\\\": \\\"chrome:headless\\\"}\"",
31 | "test-firefox-headless": "./node_modules/.bin/cucumber-js.cmd --world-parameters \"{\\\"browser\\\": \\\"firefox:headless\\\"}\""
32 | },
33 | "dependencies": {
34 | "base64-img": "^1.0.4",
35 | "cucumber": "^5.1.0",
36 | "eslint": "^4.19.1",
37 | "npm": "^6.0.0",
38 | "testcafe": "^1.1.0"
39 | },
40 | "devDependencies": {
41 | "eslint-config-google": "^0.9.1"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/reports/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rquellh/testcafe-cucumber/11644e30aacf353ad13208f37cafe3e49d922f57/reports/.gitkeep
--------------------------------------------------------------------------------