Running Puppeteer Tests on GitLab CI
In this article, we’ll walk through setting up GitLab CI to run Puppeteer tests in headless mode using a lightweight Alpine-based Node.js image. We’ll also include a sample page object for the SauceDemo login page.
Prerequisites
- A GitLab repository with a Node.js project.
- Basic knowledge of Puppeteer and GitLab CI/CD.
- A test script (using Puppeteer) and a Page Object Model (POM) file for better maintainability.
GitLab CI Configuration
We will use the node:16-alpine
Docker image to run our tests. Alpine images are lightweight and fast, which is ideal for CI environments. Because Puppeteer normally downloads its own version of Chromium, we can save build time by preventing that download and instead using the system-installed Chromium.
Create a .gitlab-ci.yml
file in your project root with the following content:
image: node:16-alpine
variables:
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: "true"
PUPPETEER_EXECUTABLE_PATH: "/usr/bin/chromium-browser"
stages:
- test
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
test:
stage: test
before_script:
- apk update
- apk add --no-cache chromium nss freetype harfbuzz ca-certificates ttf-freefont
- npm install
script:
- npm test
Explanation
Image & Variables:
- We use the
node:16-alpine
image for a small footprint. PUPPETEER_SKIP_CHROMIUM_DOWNLOAD
is set to"true"
to skip Chromium download during installation.PUPPETEER_EXECUTABLE_PATH
tells Puppeteer where Chromium is installed on Alpine.
Stages & Cache:
- We define a single
test
stage. - The
cache
section cachesnode_modules
to speed up builds.
Before Script:
apk update
updates the Alpine package lists.apk add --no-cache chromium nss freetype harfbuzz ca-certificates ttf-freefont
installs Chromium and its necessary dependencies.npm install
installs your project dependencies.- Script:
- The
npm test
command runs your test suite. Ensure yourpackage.json
is set up accordingly (for example, with a script"test": "node runTests.js"
).
Creating a Page Object
We use the Page Object Model (POM) to encapsulate page-specific logic. Below is a sample page object for the SauceDemo login page. Create a file at pages/loginPage.js
:
// pages/loginPage.js
class LoginPage {
/**
* @param {puppeteer.Page} page - The Puppeteer page instance.
*/
constructor(page) {
this.page = page;
this.usernameInput = '#user-name';
this.passwordInput = '#password';
this.loginButton = '#login-button';
}
async navigate() {
await this.page.goto('https://www.saucedemo.com/', { waitUntil: 'networkidle2' });
}
async login(username, password) {
await this.page.type(this.usernameInput, username);
await this.page.type(this.passwordInput, password);
await this.page.click(this.loginButton);
}
}
module.exports = LoginPage;
How It Works
- Constructor:
The constructor receives the Puppeteerpage
object and initializes selectors for the username input, password input, and login button. - navigate():
Opens the SauceDemo site and waits until the network is idle. - login(username, password):
Fills in the username and password, then clicks the login button.
Putting It All Together
- Develop Your Tests:
Write your Puppeteer tests using the page object. For example, create a test file (e.g.,tests/loginTest.js
) that uses theLoginPage
:
// tests/loginTest.js
const { launchBrowser } = require('../helpers/browser');
const LoginPage = require('../pages/loginPage');
(async () => {
const browser = await launchBrowser();
const page = await browser.newPage();
try {
// Create an instance of LoginPage and perform test steps.
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.login('standard_user', 'secret_sauce');
// Verify the presence of the inventory list as a successful login indicator.
await page.waitForSelector('.inventory_list', { timeout: 5000 });
console.log('Login successful and inventory page loaded.');
} catch (error) {
console.error('Test failed:', error);
process.exit(1);
} finally {
await browser.close();
}
})();
- Configure Browser Helper:
Ensure you have a helper file (helpers/browser.js
) that launches Puppeteer. A simple version might look like:
// helpers/browser.js
const puppeteer = require('puppeteer');
async function launchBrowser() {
return await puppeteer.launch({
headless: true, // Disable headless mode
devtools: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-setuid-sandbox'
],
defaultViewport: null
});
}
module.exports = { launchBrowser };
I have added a single test for the Saucelabs website to validate the tests.
I have added the logs for the pipeline of the automation run.
Conclusion
By following this guide, you’ve set up GitLab CI to run Puppeteer tests using the lightweight node:16-alpine
image. This configuration leverages system-installed Chromium, caches dependencies for faster builds, and runs tests in headless mode. The sample page object for the SauceDemo login page illustrates how to encapsulate page-specific actions, making your tests cleaner and more maintainable.
Now, every push to your repository will trigger the CI pipeline, ensuring that your Puppeteer tests run automatically in GitLab CI
I have created a project on Gitlab and added the code here.
Here is the test execution link on Gitlab CI here.
Feel free to hit clap if you like the content. Happy Automation Testing :) Cheers. 👏