Add smoke test

This commit is contained in:
Krzysztof Wolski 2023-01-27 18:07:53 +01:00
parent 54e77d495e
commit fb68ded38f
15 changed files with 331 additions and 0 deletions

View file

@ -0,0 +1,27 @@
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install dependencies
run: pnpm install
- name: Install Playwright Browsers
run: pnpm dlx playwright install --with-deps
- name: Run Playwright tests
run: pnpm dlx playwright test
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30

4
apps/e2e-tests/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
node_modules/
/test-results/
/playwright-report/
/playwright/.cache/

View file

@ -0,0 +1,16 @@
import appUrls from "./urls/app-urls";
import saleorUrls from "./urls/saleor-urls";
export const appName = process.env.APP_NAME; // TODO: name should be taken from the manifest to eliminate possible footguns
export const appUrl = process.env.APP_URL;
export const instanceUrl = process.env.INSTANCE_URL;
export const dashboardUserEmail = process.env.DASHBOARD_USER_EMAIL;
export const dashboardUserPassword = process.env.DASHBOARD_USER_PASSWORD;
export const urls = {
app: {
baseUrl: appUrl,
...appUrls(appUrl)
},
saleor: saleorUrls(instanceUrl)
}

View file

@ -0,0 +1,13 @@
{
"name": "e2e-tests",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.29.2"
}
}

View file

@ -0,0 +1,100 @@
import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
const config: PlaywrightTestConfig = {
testDir: './tests',
/* Maximum time one test can run for. */
timeout: 30 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 5000
},
/* 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,
/* 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: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
// {
// name: 'firefox',
// use: {
// ...devices['Desktop Firefox'],
// },
// },
/* 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: {
// channel: 'msedge',
// },
// },
// {
// name: 'Google Chrome',
// use: {
// channel: 'chrome',
// },
// },
],
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
// outputDir: 'test-results/',
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// port: 3000,
// },
};
export default config;

View file

@ -0,0 +1,53 @@
import { expect, test } from '@playwright/test';
import { appName, urls } from '../configuration';
import { checkIfAppIsAvailable } from '../utils/check-if-app-is-available';
import { installTheApp } from '../utils/install-the-app';
import { logInIntoDashboard } from '../utils/log-in-into-dashboard';
import { randomString } from '../utils/random-string';
test("The app can be installed", async ({page}) => {
// TODO: add function to completely remove all the installed apps in the test env
// alternative: make locators tighter to restrict it only to the tested app
await logInIntoDashboard({page})
await installTheApp({page})
await checkIfAppIsAvailable({page})
})
test("Smoke test of the configuration", async ({page}) => {
await logInIntoDashboard({page})
// open app configuration view
await page.goto(urls.saleor.dashboard.apps, {timeout: 20000, waitUntil: "load"});
await page.getByText(appName).first().click()
// generate unique marker to ensure values are updated during this test run
const marker = randomString()
// fill the configuration fields
await page.frameLocator('iframe').locator('input[name="secretKey"]').click();
await page.frameLocator('iframe').locator('input[name="secretKey"]').fill('secret-key-'+marker);
await page.frameLocator('iframe').locator('input[name="searchKey"]').click();
await page.frameLocator('iframe').locator('input[name="searchKey"]').fill('search-key-'+marker);
await page.frameLocator('iframe').locator('input[name="appId"]').click();
await page.frameLocator('iframe').locator('input[name="appId"]').fill('app-id-'+marker);
await page.frameLocator('iframe').locator('input[name="indexNamePrefix"]').click();
await page.frameLocator('iframe').locator('input[name="indexNamePrefix"]').fill('prefix-'+marker);
// submit and wait for the confirmation toast
await page.mouse.wheel(0, 20); // TODO: investigate how to automatically scroll
await page.frameLocator('iframe').getByRole('button', { name: 'Save' }).click();
await page.getByText('Configuration saved!').isVisible();
// check if the data persists
await page.goto(urls.saleor.dashboard.apps, {timeout: 20000, waitUntil: "load"});
await page.getByText(appName).first().click()
// check if the form data loaded
await page.frameLocator('iframe').getByRole('button', { name: 'Save' }).isEnabled();
// check field values
await expect(await page.frameLocator('iframe').locator('input[name="secretKey"]').inputValue()).toBe("secret-key-"+marker);
await expect(await page.frameLocator('iframe').locator('input[name="searchKey"]').inputValue()).toBe("search-key-"+marker);
await expect(await page.frameLocator('iframe').locator('input[name="appId"]').inputValue()).toBe("app-id-"+marker);
await expect(await page.frameLocator('iframe').locator('input[name="indexNamePrefix"]').inputValue()).toBe("prefix-"+marker);
})

17
apps/e2e-tests/types.ts Normal file
View file

@ -0,0 +1,17 @@
export interface WebhookManifest {
name: string;
targetUrl: string;
asyncEvents: string[];
isActive: boolean;
query: string;
}
export interface Manifest {
id: string;
version: string;
name: string;
permissions: string[];
appUrl: string;
tokenTargetUrl: string;
webhooks: WebhookManifest[];
}

View file

@ -0,0 +1,7 @@
export const appUrls = (appUrl: string) => ({
manifest: new URL('/api/manifest', appUrl).href,
register: new URL('/api/register', appUrl).href
})
export default appUrls

View file

@ -0,0 +1,9 @@
export const saleorUrls = (url: string) => ({
dashboard: {
homepage: new URL('/dashboard', url).href,
apps: new URL('/dashboard/apps', url).href
},
api: new URL('/graphql/', url).href
})
export default saleorUrls

View file

@ -0,0 +1,17 @@
import { expect, Page } from "@playwright/test";
import { appName, urls } from "../configuration";
interface checkIfAppIsAvailableArgs {
page: Page
}
export const checkIfAppIsAvailable = async ({page}: checkIfAppIsAvailableArgs) => {
// got to Apps page, assuming user is logged in
await page.goto(urls.saleor.dashboard.apps, {timeout: 20000, waitUntil: "load"});
// look for a entry with name of our app
await expect(await page.getByText(appName).first()).toBeVisible()
// and confirm its installed
await expect(await page.getByText('Problem occured during installation.')).toBeHidden()
}

View file

@ -0,0 +1,5 @@
export const delay = (time) => {
return new Promise(function(resolve) {
setTimeout(resolve, time)
});
}

View file

@ -0,0 +1,22 @@
import { Page } from "@playwright/test";
import { urls } from "../configuration";
interface InstallTheAppArgs {
page: Page
}
export const installTheApp = async ({page}: InstallTheAppArgs) => {
// got to Apps page, assuming user is logged in
await page.goto(urls.saleor.dashboard.apps, {timeout: 20000, waitUntil: "load"});
// Install the app via the manifest URL
await page.locator('[data-test-id="add-app-from-manifest"]').click();
await page.getByRole('textbox').click();
await page.getByRole('textbox').fill(urls.app.manifest);
await page.locator('[data-test-id="install-app-from-manifest"]').click();
await page.getByRole('button', { name: 'Install App' }).click();
// wait for the toast
await page.getByText('App installed').isVisible();
}

View file

@ -0,0 +1,19 @@
import { Page, expect } from "@playwright/test";
import { dashboardUserEmail, dashboardUserPassword, urls } from "../configuration";
interface LogInIntoDashboardArgs {
page: Page
}
export const logInIntoDashboard = async ({page}: LogInIntoDashboardArgs) => {
await page.goto(urls.saleor.dashboard.homepage, {timeout: 20000, waitUntil: "load"});
await page.locator('[data-test-id="email"]').click();
await page.locator('[data-test-id="email"]').fill(dashboardUserEmail);
await page.locator('[data-test-id="email"]').press('Tab');
await page.locator('[data-test-id="password"]').fill(dashboardUserPassword);
await page.locator('[data-test-id="submit"]').click();
await expect(page.locator('[data-test-id="welcome-header"]')).toBeVisible()
}
export default logInIntoDashboard

View file

@ -0,0 +1 @@
export const randomString = () => Math.random().toString(36).substr(2, 5)

View file

@ -222,6 +222,12 @@ importers:
pretty-quick: 3.1.3_prettier@2.8.3
typescript: 4.8.3
apps/e2e-tests:
specifiers:
'@playwright/test': ^1.29.2
devDependencies:
'@playwright/test': 1.30.0
packages/eslint-config-custom:
specifiers:
eslint-config-next: ^13.1.3
@ -2373,6 +2379,15 @@ packages:
tiny-glob: 0.2.9
tslib: 2.4.1
/@playwright/test/1.30.0:
resolution: {integrity: sha512-SVxkQw1xvn/Wk/EvBnqWIq6NLo1AppwbYOjNLmyU0R1RoQ3rLEBtmjTnElcnz8VEtn11fptj1ECxK0tgURhajw==}
engines: {node: '>=14'}
hasBin: true
dependencies:
'@types/node': 18.11.18
playwright-core: 1.30.0
dev: true
/@repeaterjs/repeater/3.0.4:
resolution: {integrity: sha512-AW8PKd6iX3vAZ0vA43nOUOnbq/X5ihgU+mSXXqunMkeQADGiqw/PY0JNeYtD5sr0PAy51YPgAPbDoeapv9r8WA==}
dev: true
@ -7689,6 +7704,12 @@ packages:
thread-stream: 2.3.0
dev: false
/playwright-core/1.30.0:
resolution: {integrity: sha512-7AnRmTCf+GVYhHbLJsGUtskWTE33SwMZkybJ0v6rqR1boxq2x36U7p1vDRV7HO2IwTZgmycracLxPEJI49wu4g==}
engines: {node: '>=14'}
hasBin: true
dev: true
/png-js/1.0.0:
resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==}
dev: false