Skip to content

Commit

Permalink
Add Playwright to the transition repo, and run a few simple tests on …
Browse files Browse the repository at this point in the history
…github.

FIx: chairemobilite#1065
  • Loading branch information
GabrielBruno24 committed Sep 24, 2024
1 parent 43f0ecd commit 417f946
Show file tree
Hide file tree
Showing 12 changed files with 346 additions and 35 deletions.
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,8 @@ MAIL_TRANSPORT_SMTP_AUTH_PWD=password

# From email
MAIL_FROM_ADDRESS=[email protected]

#Parameters used to login to a test account in the playwright tests
PLAYWRIGHT_TEST_USER=testUser
PLAYWRIGHT_TEST_EMAIL=[email protected]
PLAYWRIGHT_TEST_PASSWORD=testPassword
40 changes: 39 additions & 1 deletion .github/workflows/transition.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,28 @@ on:
jobs:
build-and-test:
runs-on: ubuntu-latest
services:
postgres:
image: postgis/postgis
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpassword
POSTGRES_DB: testdb
strategy:
matrix:
node-version: [18.x, 20.x]
env:
PROJECT_CONFIG: ${{ github.workspace }}/examples/config.js
PG_CONNECTION_STRING_PREFIX: postgres://testuser:testpassword@localhost:5432/
PG_DATABASE_PRODUCTION: testdb
CI: true ## This is to make sure that the tests run in CI mode
steps:
- uses: actions/checkout@v4
- name: copy env file
Expand All @@ -33,8 +50,29 @@ jobs:
run: yarn build:prod
- name: Unit Test
run: yarn test
- name: UI Test
# Following configure the automated UI tests
- name: Create DB
run: yarn setup && yarn migrate
env:
NODE_ENV: production
- name: Get Playwright config
run: cp packages/chaire-lib-frontend/playwright-example.config.ts packages/chaire-lib-frontend/playwright.config.ts
- name: Create test user
run: yarn create-user --username $PLAYWRIGHT_TEST_USER --email $PLAYWRIGHT_TEST_EMAIL --password $PLAYWRIGHT_TEST_PASSWORD --admin
- name: Start application
run: yarn start &
env:
NODE_ENV: production
- name: Run tests
run: yarn test:ui
- name: Archive UI Test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-results-${{matrix.node-version}} # This is to make sure that the results are stored in a unique name
path: survey/test-results
retention-days: 2
# End of automated UI tests

code-lint:
runs-on: ubuntu-latest
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,9 @@ tests/test
profilingData*

#example apps
examples/runtime
examples/runtime

# playwright test output
**/playwright-report/
**/test-results/
**/playwright.config.ts
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"test": "yarn workspaces run test",
"test:unit": "yarn workspaces run test:unit",
"test:sequential": "yarn workspaces run test:sequential",
"test:ui": "yarn workspaces run test:ui",
"test:ui": "yarn workspace chaire-lib-frontend run test:ui",
"lint": "yarn workspaces run lint",
"format": "yarn workspaces run format",
"list-tasks": "yarn workspace transition-backend run list-tasks",
Expand Down
5 changes: 4 additions & 1 deletion packages/chaire-lib-frontend/.eslintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
lib/
node_modules/
**/__tests__
jestSetup.ts
jestSetup.ts
ui-tests/
playwright-example.config.ts
playwright.config.ts
3 changes: 2 additions & 1 deletion packages/chaire-lib-frontend/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ module.exports = {
'./jestSetup.ts'
],
testEnvironment: 'jsdom',
snapshotSerializers: ['enzyme-to-json/serializer']
snapshotSerializers: ['enzyme-to-json/serializer'],
modulePathIgnorePatterns: ["spec.ts"],
};
3 changes: 2 additions & 1 deletion packages/chaire-lib-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"test": "cross-env NODE_ENV=test jest --config=jest.config.js",
"test:unit": "cross-env NODE_ENV=test jest --config=jest.config.js",
"test:sequential": "echo 'cross-env NODE_ENV=test jest --config=jest.sequential.config.js --runInBand'",
"test:ui": "echo 'cross-env NODE_ENV=test jest --config=jest.ui.config.js'",
"test:ui": "LOCALE_DIR=$(pwd)/locales npx playwright test",
"lint": "eslint .",
"format": "prettier-eslint $PWD/'src/**/*.{ts,tsx}' --write"
},
Expand Down Expand Up @@ -69,6 +69,7 @@
"typescript": "^4.9.4"
},
"devDependencies": {
"@playwright/test": "^1.47.2",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^10.4.7",
"@types/enzyme": "^3.10.8",
Expand Down
78 changes: 78 additions & 0 deletions packages/chaire-lib-frontend/playwright-example.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import dotenv from 'dotenv';
import path from 'path';
import { defineConfig, devices } from '@playwright/test';

dotenv.config({ path: path.resolve(__dirname, '../../.env') });

export default defineConfig({
testDir: './ui-tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
// Each test is given 15 seconds.
timeout: 15000,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:8080',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
screenshot: 'only-on-failure'
},

/* Configure projects for major browsers */
projects: [
// {
// name: 'chromium',
// use: { ...devices['Desktop Chrome'] },
// },
{
name: 'Google Chrome',
use: { ...devices['Desktop Chrome'], channel: 'chrome' }
}
// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },

// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],

/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://127.0.0.1:3000',
// reuseExistingServer: !process.env.CI,
// },
});
21 changes: 21 additions & 0 deletions packages/chaire-lib-frontend/ui-tests/example.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { test } from '@playwright/test';
import * as testHelpers from './testHelpers';
import * as loginTestHelpers from './loginTestHelpers';

const context = {
page: null as any,
title: '',
widgetTestCounters: {}
};

// Configure the tests to run in serial mode (one after the other)
test.describe.configure({ mode: 'serial' });

test.beforeAll(async ({ browser }) => {
context.page = await testHelpers.initializeTestPage(browser);
});

loginTestHelpers.startAndLoginAnonymously({ context, title: 'Transition' });


loginTestHelpers.logout({ context });
38 changes: 38 additions & 0 deletions packages/chaire-lib-frontend/ui-tests/loginTestHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2024, Polytechnique Montreal and contributors
*
* This file is licensed under the MIT License.
* License text available at https://opensource.org/licenses/MIT
*/
import * as testHelpers from './testHelpers';

/**
* Test the survey's home page, login page, until the first section's page is
* opened
* @param {string} title - The title of the survey
*/
export const startAndLoginAnonymously = ({
context,
title
}: { title: string} & testHelpers.CommonTestParameters) => {
// Test the survey landing page
testHelpers.hasTitleTest({ context, title });
testHelpers.hasUrlTest({ context, expectedUrl: '/login' });
testHelpers.hasFrenchTest({ context });
testHelpers.switchToEnglishTest({ context });
testHelpers.loginTest({ context });

// // Test the login page
// testHelpers.registerWithoutEmailTest({ context });

// // Test the home section page
// if (hasUser) {
// testHelpers.hasUserTest({ context });
// }
};


export const logout = ({ context }: testHelpers.CommonTestParameters) => {
// Test the survey logout page
testHelpers.logoutTest({ context });
};
125 changes: 125 additions & 0 deletions packages/chaire-lib-frontend/ui-tests/testHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright 2024, Polytechnique Montreal and contributors
*
* This file is licensed under the MIT License.
* License text available at https://opensource.org/licenses/MIT
*/
import moment from 'moment';
import { test, expect, Page, Browser, Locator } from '@playwright/test';
import { env } from 'process';


// Types for the tests
export type CommonTestParameters = {
context: {
// The main test page
page: Page;
// Store a counter for test names, to avoid duplicate test names. We have many objects to test and they may result in identical test names.
widgetTestCounters: { [testKey: string]: number };
};
};
type Value = string;
type StringOrBoolean = string | boolean;
type Text = string;
type Url = string;
type Title = string;
type Path = string;
type Email = string;
type PathAndValue = { path: Path; value: Value };
type PathAndValueBoolOrStr = { path: Path; value: StringOrBoolean };
type HasTitleTest = (params: { title: Title } & CommonTestParameters) => void;
type HasFrenchTest = (params: CommonTestParameters) => void;
type SwitchToLanguageTest = (params: CommonTestParameters) => void;
type HasUrlTest = (params: { expectedUrl: Url } & CommonTestParameters) => void;
type LoginTest = (params: CommonTestParameters) => void;
type LogoutTest = (params: CommonTestParameters) => void;


/**
* Open the browser before all the tests and go to the home page
*
* @param {Browser} browser - The test browser object
* @param {Object} options - The options for the test.
* @param {{ [param: string]: string} } options.urlSearchParams - Additional
* parameters to add to the URL as query string question.
* @param {boolean} options.ignoreHTTPSErrors - Whether to ignore HTTPS errors.
* These can happen if running the tests on a remote server with HTTPs (for
* example test instances)
*/
export const initializeTestPage = async (
browser: Browser,
options: { urlSearchParams?: { [param: string]: string }, ignoreHTTPSErrors?: boolean } = {}
): Promise<Page> => {
const context = await browser.newContext({ ignoreHTTPSErrors: options.ignoreHTTPSErrors === true });
const page = await context.newPage();

const baseUrlString = test.info().project.use.baseURL;
if (typeof baseUrlString === 'string' && options.urlSearchParams) {
// Add the search params to the base URL
const baseURL = new URL(baseUrlString);
Object.keys(options.urlSearchParams).forEach((param) => {
baseURL.searchParams.append(param, options.urlSearchParams![param]);
});
await page.goto(baseURL.toString());
} else {
// Go to home page
await page.goto('/');
}

return page;
};

export const hasTitleTest: HasTitleTest = ({ context, title }) => {
test(`Has title ${title}`, async () => {
await expect(context.page).toHaveTitle(title);
});
};

export const hasUrlTest: HasUrlTest = ({ context, expectedUrl }) => {
test(`Current page has the url ${expectedUrl}`, async () => {
await expect(context.page).toHaveURL(expectedUrl);
});
};

// Test if the page has a french language
export const hasFrenchTest: HasFrenchTest = ({ context }) => {
test('Has French language', async () => {
const englishButton = context.page.getByRole('button', { name: 'English' });
await expect(englishButton).toHaveText('English');
});
};

/**
* Test if the page can switch to English language.
*
* @param {Object} context - The test context.
* @param {Object} context.page - The page object from the test context.
*/
export const switchToEnglishTest: SwitchToLanguageTest = ({ context }) => {
test('Switch to English language', async () => {
const englishButton = context.page.getByRole('button', { name: 'English' });
await englishButton.click();
const frenchButton = context.page.getByRole('button', { name: 'Français' });
await expect(frenchButton).toHaveText('Français');
});
};

export const loginTest: LoginTest = ({ context }) => {
test(`Login to the test account`, async () => {
const userNameField = context.page.locator("id=usernameOrEmail");
await userNameField.fill(process.env.PLAYWRIGHT_TEST_USER as string);
const passwordField = context.page.locator("id=password");
await passwordField.fill(process.env.PLAYWRIGHT_TEST_PASSWORD as string);
const loginButton = context.page.getByRole('button', { name: 'Login' });
await loginButton.click();
//await expect(context.page).toHaveURL('/dashboard');
});
};

export const logoutTest: LogoutTest = ({ context }) => {
test('Logout from survey', async () => {
const logoutButton = context.page.getByRole('button', { name: 'Logout' });
await logoutButton.click();
await expect(context.page).toHaveURL('/login');
});
};
Loading

0 comments on commit 417f946

Please sign in to comment.