Skip to content
This repository has been archived by the owner on Sep 6, 2024. It is now read-only.

Implementing Tests #39

Merged
merged 16 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ const ontarioCreateAppTemplates = (answers) => [
{ template: 'fr.njk', outputDir: 'src', outputFile: `${answers.frPage}.njk` },
{ template: 'sitemap.njk', outputDir: 'src', outputFile: 'sitemap.njk' },
{ template: 'globals.njk', outputDir: 'src/_data', outputFile: 'globals.js' },
{ template: 'env.njk', outputDir: '', outputFile: '.env' },
{ template: 'gitignore.njk', outputDir: '', outputFile: '.gitignore' },
{ template: '/tests/unit/app.spec.njk', outputDir: 'src/tests/_unit', outputFile: 'app.spec.js' },
{ template: '/tests/integration/packages.spec.njk', outputDir: 'src/tests/integration', outputFile: 'packages.spec.js' },
{ template: '/tests/e2e/nightwatch.conf.njk', outputDir: '', outputFile: 'nightwatch.conf.js' },
{ template: '/tests/e2e/prompt.spec.njk', outputDir: 'src/tests/e2e', outputFile: '_prompt.spec.js' },
{ template: '/tests/e2e/localization.spec.njk', outputDir: 'src/tests/e2e', outputFile: 'localization.spec.js' },
{ template: '/tests/e2e/search.spec.njk', outputDir: 'src/tests/e2e', outputFile: 'search.spec.js' }
];

module.exports = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
E2E_URL=http://localhost:8080
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.env
dist/
logs/
node_modules/
tests_output/
DS_Store
syed-ods marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"scripts": {
"build": "rm -rf dist && eleventy",
"debug": "rm -rf dist && DEBUG=* eleventy",
"serve": "eleventy --serve"{% if addESLint %},
"serve": "eleventy --serve",
"test": "npm list mocha chai || npm install mocha [email protected]; mocha './src/tests/**/*.spec.js'",
"test:e2e": "npm list nightwatch || npm install nightwatch && npm install chromedriver && if [ -z $npm_config_url ]; then npx nightwatch; else E2E_URL=$npm_config_url npx nightwatch; fi"{% if addESLint %},
"lint": "eslint . --ext .js"{% endif %}{% if addPrettier %},
"format": "prettier --write ."{% endif %}
},
Expand All @@ -18,7 +20,5 @@
"devDependencies": {
{% for dep, version in devDependencies %}"{{ dep }}": "{{ version }}"{% if not loop.last or addESLint or addPrettier %},{% endif %}
{% endfor %}{% if addESLint %}{% for dep, version in eslintDependencies %}"{{ dep }}": "{{ version }}"{% if not loop.last or addPrettier %},{% endif %}
{% endfor %}{% endif %}{% if addPrettier %}{% for dep, version in prettierDependencies %}"{{ dep }}": "{{ version }}"{% if not loop.last %},{% endif %}
{% endfor %}
{% endif %}}
{% endfor %}{% endif %}{% if addPrettier %}{% for dep, version in prettierDependencies %}"{{ dep }}": "{{ version }}"{% if not loop.last %},{% endif %}{% endfor %}{% endif %}}
}
syed-ods marked this conversation as resolved.
Show resolved Hide resolved
syed-ods marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{# Localization End to End tests to check if the basic language toggle between EN and FR is working #}/**
* E2E tests that check if the localization content is working, including the language toggle button.
*/
syed-ods marked this conversation as resolved.
Show resolved Hide resolved

/**
* Helper function to check if element exists and is visible
* @param {string} selector - Selector value, either a class or an id, of the element
* @param {string} description - Description of the element
*/
function checkElement(selector, description) {
browser.assert
.elementPresent(selector, `${description} exists`)
.assert.visible(selector, `${description} is visible`);
}
syed-ods marked this conversation as resolved.
Show resolved Hide resolved

/**
* Helper function to check the language attributes and button text
* @param {string} langAttr - Expected value of the HTML lang attribute
* @param {string} buttonText - Expected text content of the language toggle button
*/
function checkLanguageAttributes(langAttr, buttonText) {
browser.assert
.attributeEquals("html", "lang", langAttr) // Check if the HTML lang attribute matches the expected value
.getText(".ontario-header__language-toggler span", function (result) {
this.assert.equal(result.value.trim(), buttonText); // Check if the button text matches the expected value
syed-ods marked this conversation as resolved.
Show resolved Hide resolved
});
}
syed-ods marked this conversation as resolved.
Show resolved Hide resolved

/**
* 1. Checks if the html attribute of the page has the right attributes.
* 2. If the language button toggles.
* 3. If the language button has the right text content.
*
* Variables and parameters for the e2e tests
* @param {string} E2E_URL - The saved env variable path of the link to be tested, local, staging, or prod.
* @param {string} langButton - The clickable link/button to toggle between EN & FR.
* @param {string} enLangAttr - The English language HTML attribute value.
* @param {string} frLangAttr - The French language HTML attribute value.
* @param {string} frButtonText - The language toggle button value to French.
* @param {string} enButtonText - The language toggle button value to English.
*/

module.exports = {
before: function (browser) {
// Actions to perform before the test suite runs
browser.maximizeWindow();
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good use of before hook here.

Suggestion: Ad a check to ensure E2E_URL is defined and accessible:

if (!E2E_URL) {
    throw new Error("E2E_URL is not defined. Please set the environment variable.");
}


"E2E: Localization test: English and French": function (browser) {
const E2E_URL = process.env.E2E_URL;
const langButton = ".ontario-header__language-toggler"
const enLangAttr = "en";
const frLangAttr = "fr";
const frButtonText = "Français";
const enButtonText = "English";

browser
.url(E2E_URL)
.waitForElementVisible("body", 2000)

// Check if search elements exist and are visible
.perform(() => checkElement(langButton, "Language toggle button"));

// Initial check for English
checkLanguageAttributes(enLangAttr, frButtonText); // Check English attributes and French button text

// Toggle to French and check
browser.click(langButton); // Click the language toggle button to switch to French
checkLanguageAttributes(frLangAttr, enButtonText); // Check French attributes and English button text

// Toggle back to English and check
browser.click(langButton); // Click the language toggle button to switch back to English
checkLanguageAttributes(enLangAttr, frButtonText); // Check English attributes and French button text
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The checks for toggling between languages are well-implemented.

Consider adding a short wait between clicks to ensure the page has time to update:

browser.pause(500); // Wait 0.5 seconds before checking the next language state


browser.end();
},
};
syed-ods marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module.exports = {
src_folders: ["src/tests/e2e/**/**.spec.js"],

webdriver: {
start_process: true,
server_path: require("chromedriver").path,
port: 9515,
syed-ods marked this conversation as resolved.
Show resolved Hide resolved
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good setup with chromedriver. Ensure the chromedriver version is compatible with the installed Chrome version.

Idea: Add an environment variable for server_path to easily switch drivers if needed.

server_path: process.env.CHROMEDRIVER_PATH || require("chromedriver").path,


test_settings: {
default: {
desiredCapabilities: {
browserName: "chrome",
},
},
},
};
syed-ods marked this conversation as resolved.
Show resolved Hide resolved
syed-ods marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{# CLI prompts for the user when they run the command npm test:e2e to run our end to end tests #}/**
* E2E test prompts to give user feedback about the command structure and how to run the tets.
*/
let E2E_URL = process.env.E2E_URL
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure the E2E_URL env var is defined with a conditional check:

if (!E2E_URL) {
    console.error(`${fgYellow}E2E_URL is not defined. Please set the environment variable in your .env file.${reset}`);
    process.exit(1);
}


const reset = "\x1b[0m";
const fgCyan = "\x1b[36m";
const fgYellow = "\x1b[33m";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Chalk.js which is installed already and available through some utils.


console.log(`\n${fgCyan}Make sure to run the project locally or have a working URL before running this test.${reset}`);
console.log(`E2E tests run on ${fgCyan}Google Chrome${reset}.`);
console.log(`The E2E tests will run on the saved URL in the environment variables ${fgCyan}E2E_URL${reset}=${fgCyan}${E2E_URL}${reset}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a desc of what the tests are doing:

console.log(`E2E tests will run automated checks to ensure the application's functionality is working as expected.`);

console.log(`You can update the ${fgCyan}E2E_URL${reset} in your local .env file`);
console.log(`To run the tests on a target URL, use the command: ${fgYellow}npm run test:e2e ${fgCyan}--url=(Your-Site-URL)${reset}`);
console.log(`Example: ${fgYellow}npm run test:e2e${reset} ${fgCyan}--url=https://www.ontario.ca/international-trade-events-calendar${reset}\n`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a note that they should replace the placeholder url

console.log(`(Replace the placeholder URL with the actual URL you want to test)`);

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{# Search End to End and integration tests to check if the search takes us to the expected ontario.ca URL #}/**
* E2E tests that checks the functionality of the search bar.
*/

/**
* Helper function to check if element exists and is visible
* @param {string} selector - Selector value, either a class or an id, of the element
* @param {string} description - Description of the element
*/
function checkElement(selector, description) {
browser.assert
.elementPresent(selector, `${description} exists`)
.assert.visible(selector, `${description} is visible`);
}
syed-ods marked this conversation as resolved.
Show resolved Hide resolved

/**
* 1. Checks if the search input and the search button exist.
* 2. Types in a search term and checks if the page gets redirected to the expected URL.
*
* Variables and parameters for the e2e tests
* @param {string} E2E_URL - The saved env variable path of the link to be tested, local, staging, or prod.
* @param {string} searchTerm - The search text to be typed in the search box.
* @param {string} expectedSearchURL - The URL that the search term is supposed to be redirected to.
* @param {string} searchInputSelector - The selector for the search field.
* @param {string} expectedSearchURL - The selector for the search submit button.
*/

module.exports = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could have some edge cases covered?

'E2E: Empty Search Term': function (browser) {
    browser
        .url(E2E_URL)
        .waitForElementVisible('body', 2000)
        .perform(() => checkElement(searchInputSelector, 'Search input field'))
        .perform(() => checkElement(searchButtonSelector, 'Search button'))
        .click(searchButtonSelector)
        .assert.containsText('#error-message', 'Please enter a search term.')
        .end();
}

before: function (browser) {
// Actions to perform before the test suite runs
browser.maximizeWindow();
},

"E2E: Search Bar Test": function (browser) {
const E2E_URL = process.env.E2E_URL;
const searchTerm = "license";
const expectedSearchURL =
"https://www.ontario.ca/search/search-results/?query=license";
const searchInputSelector = "#ontario-search-input-field";
const searchButtonSelector = "#ontario-search-submit";

browser
.url(E2E_URL)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!E2E_URL) {
    console.error('E2E_URL is not defined. Please set the environment variable in your .env file.');
    process.exit(1);
}

.waitForElementVisible("body", 2000, "Body is visible")

// Check if search elements exist and are visible
.perform(() => checkElement(searchInputSelector, "Search input field"))
.perform(() => checkElement(searchButtonSelector, "Search button"))

// Type in the search term and check redirection
.setValue(searchInputSelector, searchTerm)
.click(searchButtonSelector)
.waitForElementVisible("body", 2000, "Body is visible after search")
.assert.urlContains(
expectedSearchURL,
"Redirected to the correct search URL"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.assert.urlContains(expectedSearchURL, 'Should redirect to the correct search URL')

Could use clearer language

)
.end();
},
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The integration tests are well-structured, and the use of helper functions enhances readability. Good job on ensuring coverage for both required and optional packages.

Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
{# Integration tests for projects created using Ontario Frontend #}/**
* Integration tests that check if the required and selected node modules were installed
*/
const { expect } = require("chai");
const fs = require("fs");
const path = require("path");

/** Tests for required and optional packages
* 1. Checks if the required ontario-frontend core package is installed.
* 2. If selected, the optional ES-Lint or Prettier packages were installed with their associated packages and dependencies.
*
* @param {string} package - Name of the node package/module.
* @param {object} optionalPackages - Name of the optional packages like ES-lint and Prettier.
* @param {boolean} optionalPackages.enabled - Boolean value if the optional packages were selected or not through the CLI prompt.
* @param {array} optionalPackages.packages - A list of associated packages for a parent package.
* The boolean value should be captured from the CLI prompts.
*
* @returns {boolean} Based on the test, the result will either be TRUE or FALSE (error).
*/

// A utility function to read package.json
const readPackageJson = () => {
const packageJsonPath = path.resolve(__dirname, "../../..", "package.json");
return JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
};
syed-ods marked this conversation as resolved.
Show resolved Hide resolved

// Checks if a package exists in package.json both in dependencies and dev dependencies
const isPackageInstalled = (pkg) => {
const packageJson = readPackageJson();
return (
(packageJson.dependencies &&
packageJson.dependencies.hasOwnProperty(pkg)) ||
(packageJson.devDependencies &&
packageJson.devDependencies.hasOwnProperty(pkg))
);
};
syed-ods marked this conversation as resolved.
Show resolved Hide resolved

describe("Integration: Installed packages", function () {
// Array of required packages – can add more to the list
const requiredPackages = ["@ongov/ontario-frontend"];

// Testing the required packages
requiredPackages.forEach((pkg) => {
it(`The core package "${pkg}" should be installed`, function () {
try {
if (!isPackageInstalled(pkg)) {
throw new Error();
}
} catch (e) {
expect.fail(`Failed: to detect the required package: "${pkg}"`);
}
});
});
syed-ods marked this conversation as resolved.
Show resolved Hide resolved

// The optional packages and their associated packages
const optionalPackages = {
eslint: {
enabled: {{ addESLint }},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure CLI prompts correctly set these values before running the tests. Add a check that confirms the values are defined, example:

if (typeof addESLint === 'undefined' || typeof addPrettier === 'undefined') {
    console.error('addESLint or addPrettier is not defined. Please ensure the CLI prompts set these values correctly.');
    process.exit(1);
}

packages: [
"eslint",
"eslint-plugin-import",
"@ongov/eslint-config-ontario-frontend",
syed-ods marked this conversation as resolved.
Show resolved Hide resolved
],
},
prettier: {
enabled: {{ addPrettier }},
packages: ["prettier", "@ongov/prettier-config-ontario-frontend"],
},
};

// Testing optional packages with their associated packages and dependencies
for (const [parentPackage, config] of Object.entries(optionalPackages)) {
if (config.enabled) {
config.packages.forEach((pkg) => {
it(`The package "${pkg}" should be installed for "${parentPackage}"`, function () {
try {
if (!isPackageInstalled(pkg)) {
throw new Error();
}
} catch (e) {
expect.fail(
`Failed: couldn't locate the package: "${pkg}" for "${parentPackage}"`,
);
}
});
});
}
}
});
Loading