GithubHelp home page GithubHelp logo

orca-scan / puppeteer-cucumber-js Goto Github PK

View Code? Open in Web Editor NEW
8.0 5.0 11.0 4.31 MB

Write browser automation tests using puppeteer and cucumber-js

License: ISC License

Gherkin 2.64% JavaScript 97.36%
puppeteer cucumber-js testing

puppeteer-cucumber-js's Introduction

Build node-current License: MIT Puppeteer API

Browser Automation framework using puppeteer and cucumber-js.

Works with Chrome, Firefox, Microsoft Edge and Brave.

Table of Contents

Installation

npm install puppeteer-cucumber-js

Usage

node ./node_modules/puppeteer-cucumber-js/index.js # path to the module within your project

Options

--tags <@tagname>               # cucumber @tag name to run
--featureFiles <path>           # comma-separated list of feature files or path to directory
--browser <name>                # browser to use (chrome, firefox, edge, brave). default chrome
--browserPath <path>            # optional path to a browser executable
--browser-teardown <optional>   # browser cleanup after each scenario (always, clear, none). default always
--headless                      # run browser in headless mode. defaults to false
--devTools                      # open dev tools with each page. default false
--noScreenshot                  # disable auto capturing of screenshots with errors
--disableLaunchReport           # disable auto opening the browser with test report
--timeOut <n>                   # steps definition timeout in milliseconds. defaults 10 seconds
--worldParameters <JSON>        # JSON object to pass to cucumber-js world constructor
--version                       # outputs puppeteer-cucumber-js version number
--help                          # list puppeteer-cucumber-js options
--failFast                      # abort the run on first failure
--slowMo <n>                    # specified amount of milliseconds to slow down Puppeteer operations by. defaults to 10 ms
--networkSpeed <name>           # simulate network speed (gprs, 2g, 3g, 4g, dsl, wifi). default off

Browser teardown strategy

The browser automatically closes after each scenario to ensure the next scenario uses a fresh browser environment. You can change this behavior using the --browser-teardown switch, options are:

Value Description
always the browser automatically closes (default)
clear the browser automatically clears cookies, local and session storages
none the browser does nothing

Directory structure

Your files must live in a features folder within the root of your project:

.
└── features
    ├── google-search.feature
    └── step-definitions
    │   └── google-search-steps.js
    ├── page-objects
    │   └── google-search.js
    ├── shared-objects
    │   └── test-data.js
    └── reports                     # folder and content automatically created when tests run
        ├── cucumber-report.html
        ├── cucumber-report.json
        └── junit-report.xml

Feature files

A Feature file is a Business Readable file that lets you describe software behavior without detailing how that behavior is implemented. Feature files are written using the Gherkin syntax.

Feature: Searching for a barcode scanner app
  
  Scenario: Google search for barcode scanner app
    Given I am online at google.co.uk
    When I search Google for "barcode scanner app"
    Then I should see "Orca Scan" in the results

  Scenario: Google search for Orca Scan
    Given I am online at google.co.uk
    When I search Google for "Orca Scan"
    Then I should see "Orca Scan" in the results

Step definitions

Step definitions act as the glue between features files and the actual system under test. To avoid confusion always return a JavaScript promise from step definitions to let cucumber know when your task has completed.

this.Given(/^I am online at google.co.uk/, function() {

    // use the ./page-objects/google-search.js url property
    return helpers.loadPage(pageObjects.googleSearch.url);
});

this.When(/^I search Google for "([^"]*)"$/, function (searchQuery) {

    // execute ./page-objects/google-search.js preformSearch method
    return pageObjects.googleSearch.preformSearch(searchQuery);
});

this.Then(/^I should see "([^"]*)" in the results$/, function (keywords) {

    // resolves if an item on the page contains text
    return helpers.waitForLinkText(keywords, false, 30);
});

The following variables are available within the Given(), When() and Then() functions:

Variable Description
helpers a collection of helper methods things puppeteer does not provide but maybe should
puppeteer the raw puppeteer object
browser instance of the puppeteer browser object
page instance of the puppeteer page object
pageObjects collection of page objects loaded from disk and keyed by filename
shared collection of shared objects loaded from disk and keyed by filename
trace handy trace method to log console output with increased visibility
assert instance of chai assert to assert.isOk('everything', 'everything is ok')
expect instance of chai expect to expect('something').to.equal('something')

Page objects

Page objects allow you to define information about a specific page in one place such as selector, methods etc. These objects are accessible from within your step definition files and help to reduce code duplication. Should your page change, you can fix your tests by modifying the selectors in one location.

You can access page object properties and methods via a global pageObject variable. Page objects are loaded from ./features/page-objects folder and are exposed as a camel-cased version of their filename, for example ./page-objects/google-search.js becomes pageObjects.googleSearch. You can also use subdirectories, for example ./page-objects/dir/google-search.js becomes pageObjects.dir.googleSearch.

Page objects also have access to the same runtime variables available to step definitions.

An example page object:

let image;
module.exports = {

    url: 'http://www.google.co.uk',

    selectors: {
        searchInput: '[name="q"]',
        searchResultLink: 'a > h3 > span',
        cookieIFrame: 'iframe[src*="consent.google.com"]',
        cookieAgreeButton: '#introAgreeButton > span > span'
    },

    /**
     * enters a search term into Google's search box and presses enter
     * @param {string} searchQuery - phrase to search google with
     * @returns {Promise} a promise to enter the search values
     */
    preformSearch: async function (searchQuery) {
        image = searchQuery;
        // get the selector above (pageObjects.googleSearch is this object)
        var selector = pageObjects.googleSearch.selectors.searchInput;
        await helpers.takeImage(`${image}_1-0.png`);
        
        // accept Googles `Before you continue` cookie dialog
        await helpers.clickElementWithinFrame(pageObjects.googleSearch.selectors.cookieIFrame, pageObjects.googleSearch.selectors.cookieAgreeButton);

        // set focus to the search box
        await page.focus(selector);

        // enter the search query
        await page.keyboard.type(searchQuery, { delay: 100 });

        // press enter
        await helpers.compareImage(`${image}_1-0.png`);
        return page.keyboard.press('Enter');
    }
};

Shared objects

Shared objects allow you to share anything from test data to helper methods throughout your project via a global sharedObjects object. Shared objects are automatically loaded from ./features/shared-objects/ and made available via a camel-cased version of their filename, for example ./features/shared-objects/test-data.js becomes sharedObjects.testData. You can also use subdirectories, for example ./features/shared-objects/dir/test-data.js becomes sharedObjects.dir.testData.

Shared objects also have access to the same runtime variables available to step definitions.

An example shared object:

module.exports = {
    username: "import-test-user",
    password: "import-test-pa**word"
}

And its usage within a step definition:

module.exports = function () {

    this.Given(/^I am logged in"$/, function () {

        // set focus to username
        await page.focus('#username');

        // type username
        await page.keyboard.type(sharedObjects.testData.username);

        // set focus to password
        await page.focus('#password');

        // type password
        await page.keyboard.type(sharedObjects.testData.password);

        // press enter (submit form)
        return page.keyboard.press('Enter');
    });
};

Helpers

Helpers are globally defined helper methods that simplify working with puppeteer:

// Load a URL, returning only when all network activity has finished
helpers.loadPage('http://www.google.com');

// Open a URL in a new tab or switch to the tab that already has it open and 
// set it's instance as the global page variable.
helpers.openPage('http://www.yahoo.com');

// Removes an element from the dom
helpers.removeElement('p > span');

// Waits for text to appear on the page
helpers.waitForLinkText('Orca Scan', false, 30);

// Waits for the browser to fire an event (including custom events)
helpers.waitForEvent('app-ready');

// Gets an element within an iframe
helpers.getElementWithinFrame('iframe[src*="consent.google.com"]', '#introAgreeButton > span > span');

// Clicks an element within an iframe
helpers.clickElementWithinFrame('iframe[src*="consent.google.com"]', '#introAgreeButton > span > span');

// Removes all browser cookies
helpers.clearCookies();

// Clears localStorage
helpers.clearLocalStorage();

// Clears sessionStorage
helpers.clearSessionStorage();

// Clears cookies and storage
helpers.clearCookiesAndStorages();

// Stop the browser in debug mode (must have DevTools open)
helpers.debug()

// take image for comparisson
helpers.takeImage('image_1-0.png', ['dynamic elements to hide']);

// compare taken image with baseline image
helpers.compareImage('image_1-0.png');

Before/After hooks

You can register before and after handlers for features and scenarios:

Event Example
BeforeFeature this.BeforeFeatures(function(feature, callback) {})
AfterFeature this.AfterFeature(function(feature, callback) {});
BeforeScenario this.BeforeScenario(function(scenario, callback) {});
AfterScenario this.AfterScenario(function(scenario, callback) {});
module.exports = function () {

    // add a before feature hook
    this.BeforeFeature(function(feature, done) {
        console.log('BeforeFeature: ' + feature.getName());
        done();
    });

    // add an after feature hook
    this.AfterFeature(function(feature, done) {
        console.log('AfterFeature: ' + feature.getName());
        done();
    });

    // add before scenario hook
    this.BeforeScenario(function(scenario, done) {
        console.log('BeforeScenario: ' + scenario.getName());
        done();
    });

    // add after scenario hook
    this.AfterScenario(function(scenario, done) {
        console.log('AfterScenario: ' + scenario.getName());
        done();
    });
};

Visual Regression

Visual regression testing, the ability to compare a whole page screenshots or of specific parts of the application / page under test. If there is dynamic content (i.e. a clock), hide this element by passing the selector (or an array of selectors, comma separated) to the takeImage function.

// usage within page-object file:
  await helpers.takeImage(fileName, [elementsToHide, elementsToHide]);
  await page.waitForTimeout(100);
  await helpers.compareImage(fileName);

Reports

HTML, JSON and JUnit reports are auto generated with each test run and stored in ./features/reports/:

Cucumber HTML report

How to debug

To step into debug mode in the browser, enable dev tools --devTools and use helpers.debug() within your steps:

module.exports = function () {

    this.When(/^I search Google for "([^"]*)"$/, async function (searchQuery, done) {

        // Stop the browser in debug mode
        helpers.debug();
    });
};

Demo

To demo the framework without installing in your project use the following commands:

# download this example code
git clone https://github.com/orca-scan/puppeteer-cucumber-js.git

# go into the new directory
cd puppeteer-cucumber-js

# install dependencies
npm install

# run the google search feature
node index

Bugs

Please provide as much info as possible (ideally a code snippet) when raising a bug

Contributing

PRs welcome 🤓

License

Licensed under ISC License © Orca Scan, the Barcode Scanner app for iOS and Android.

puppeteer-cucumber-js's People

Contributors

john-doherty avatar larryg01 avatar mlatif01 avatar phunky avatar thu2004 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

puppeteer-cucumber-js's Issues

Running inside Docker

Docker has a specific requirements to launch browser with --no-sandbox and --disable-setuid-sandbox and I struggle to find a way of setting those options without making a fork and editing runtime/world.js.

For now I'm trying to modify global browser variable within a BeforeFeatures hook and it doesn't work.

module.exports = function () {
  this.registerHandler('BeforeFeatures', async function () {
    if (!global.browser) {
      const options = {
        args: [
          // Required for Docker version of Puppeteer
          '--headless',
          '--no-sandbox',
          '--disable-setuid-sandbox',
          // This will write shared memory files into /tmp instead of /dev/shm,
          // because Docker’s default for /dev/shm is 64MB
          '--disable-dev-shm-usage'
        ]
      }

      global.browser = await puppeteer.launch(options)
    }
  })
}

This code is located in step-definitions folder, hook is called and a browser variable contains a valid puppeteer object.
Yet the script fails with the following:

/node_modules/cucumber/lib/cucumber/runtime/event_broadcaster.js:30
            process.nextTick(function(){ throw error; }); // prevent swallow by unhandled rejection
                                         ^

Error: node_modules/puppeteer-cucumber-js/runtime/world.js:99 Failed to launch the browser process!
[48:48:0824/105830.997835:ERROR:zygote_host_impl_linux.cc(90)] Running as root without --no-sandbox is not supported. See https://crbug.com/638180.

TROUBLESHOOTING: https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md
    at onClose (/node_modules/puppeteer-cucumber-js/node_modules/puppeteer/lib/cjs/puppeteer/node/BrowserRunner.js:193:20)
    at Interface.<anonymous> (/node_modules/puppeteer-cucumber-js/node_modules/puppeteer/lib/cjs/puppeteer/node/BrowserRunner.js:183:68)
    at Interface.emit (events.js:327:22)
    at Interface.close (readline.js:424:8)
    at Socket.onend (readline.js:202:10)
    at Socket.emit (events.js:327:22)
    at endReadableNT (internal/streams/readable.js:1327:12)
    at processTicksAndRejections (internal/process/task_queues.js:80:21)

Any ideas?)

compatibility with latest cucumber js version

I am facing few issues when upgrades to latest cucumber version (version 6)

please can you recommend what changes will i need to do make it working with latest cucumber version (especially index.js file)

I really appreciate your help

Thanks,
Anil

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.