@spscommerce/e2e-core 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.dockerignore CHANGED
@@ -1,8 +1,7 @@
1
1
  # Ignore all files by default
2
2
  *
3
3
  # White list the required files
4
- !auth.json
5
- !env.config.ts
4
+ !helpers/
6
5
  !credentials.sh
7
6
  !jest-environment.config.js
8
7
  !jest-playwright.config.js
package/Dockerfile CHANGED
@@ -3,7 +3,8 @@ FROM mcr.microsoft.com/playwright:bionic
3
3
  # We need to install Edge manually since it does not exist in the official PW image
4
4
  # https://github.com/microsoft/playwright/blob/07fb81a4efdcc4c2169a61a16b513b9d91be6c49/src/server/chromium/findChromiumChannel.ts#L38
5
5
 
6
- RUN apt-get install wget \
6
+ RUN apt-get update \
7
+ && apt-get install --reinstall wget \
7
8
  && wget -q -O - https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \
8
9
  && echo "deb https://packages.microsoft.com/repos/edge stable main" >> /etc/apt/sources.list.d/microsoft-edge.list \
9
10
  && apt-get update -qqy \
package/README.md CHANGED
@@ -39,7 +39,7 @@ HEADLESS=false BROWSER=chromium npm run test
39
39
  | ------------------: | -------------------------------------------------- | ------------------------------ | ------------------------------------ |
40
40
  | HEADLESS | launch browser in headless/headful mode | true | true, false |
41
41
  | BROWSER | use + as a separator, e.g edge+firefox | chromium | chromium, firefox, webkit, edge |
42
- | ENV | auth state is invoked for the defined env | test | test, prod |
42
+ | URL | auth state is invoked for the defined env | test | test, prod etc. |
43
43
  | SLOWMO | slow down an execution for debugging | 0 | [number_in_ms] |
44
44
  | TIMEOUT | global timeout for each test | 30000 | [number_in_ms] |
45
45
  | WORKERS | number of workers (test threads) | number of CPUs minus 1 | [number] |
@@ -0,0 +1,75 @@
1
+ import fetch from 'node-fetch';
2
+ import { authEndpoint } from './envSetup';
3
+
4
+ export async function getAuthToken(): Promise<string> {
5
+ if (process.env.USER_EMAIL && process.env.USER_PASSWORD) {
6
+ const requestOptions = {
7
+ method: 'POST',
8
+ body: JSON.stringify({
9
+ username: process.env.USER_EMAIL,
10
+ password: process.env.USER_PASSWORD,
11
+ client_id: "595",
12
+ grant_type: "password"
13
+ }),
14
+ headers: {
15
+ 'Accept': 'application/json',
16
+ 'Content-Type': 'application/json'
17
+ }
18
+ }
19
+ try {
20
+ const fetchResponse = await fetch(authEndpoint + 'identity/token/', requestOptions);
21
+ const data = await fetchResponse.json();
22
+ return data.access_token;
23
+ } catch (e) {
24
+ console.log(e);
25
+ return process.exit(1);
26
+ }
27
+ } else {
28
+ throw new Error(`Set up process environment variables: USER_EMAIL and USER_PASSWORD.`);
29
+ }
30
+ }
31
+
32
+ export async function getUserInfo(): Promise<any> {
33
+ const requestOptions = {
34
+ method: 'GET',
35
+ headers: {
36
+ 'Authorization': `Bearer ${process.env.TOKEN}`,
37
+ 'Accept': 'application/json',
38
+ 'Content-Type': 'application/json'
39
+ }
40
+ }
41
+ try {
42
+ const fetchResponse = await fetch(authEndpoint + `identity/users/me/`, requestOptions);
43
+ const data = await fetchResponse.json();
44
+ return data;
45
+ } catch (e) {
46
+ console.log(e);
47
+ return process.exit(1);
48
+ }
49
+ }
50
+
51
+ export async function changePreferredOrg(orgID: string): Promise<void> {
52
+ const userData = await getUserInfo();
53
+ const requestOptions = {
54
+ method: 'PUT',
55
+ body: JSON.stringify({
56
+ email: userData.email,
57
+ first_name: userData.first_name,
58
+ last_name: userData.last_name,
59
+ preferred_org_id: orgID
60
+ }),
61
+ headers: {
62
+ 'Authorization': `Bearer ${process.env.TOKEN}`,
63
+ 'Accept': 'application/json',
64
+ 'Content-Type': 'application/json'
65
+ }
66
+ }
67
+ try {
68
+ const fetchResponse = await fetch(authEndpoint + `identity/users/${userData.id}/`, requestOptions);
69
+ const data = await fetchResponse.json();
70
+ return data;
71
+ } catch (e) {
72
+ console.log(e);
73
+ return process.exit(1);
74
+ }
75
+ }
@@ -1,14 +1,17 @@
1
- import { authFile } from "../env.config";
2
- import fs from 'fs';
1
+ import { getAuthToken, changePreferredOrg } from './apiCalls';
2
+ import { primaryOrgID } from './envSetup';
3
3
 
4
- export async function authenticate(): Promise<void> {
5
- const authStorage = fs.readFileSync(authFile, 'utf8');
6
- await context.addInitScript(([storageDump]) => {
7
- const entries = JSON.parse(storageDump);
8
- Object.keys(entries).forEach(k => {
9
- window.localStorage.setItem(k, entries[k]);
10
- });
11
- }, [authStorage]);
4
+ export async function authenticate(orgID?: string): Promise<void> {
5
+ if (orgID) {
6
+ await changePreferredOrg(orgID);
7
+ process.env.TOKEN = await getAuthToken();
8
+ } else {
9
+ await changePreferredOrg(primaryOrgID);
10
+ process.env.TOKEN = await getAuthToken();
11
+ }
12
+ await page.addInitScript(params => {
13
+ window.localStorage.setItem(params.name, params.value);
14
+ }, { name: "access_token", value: process.env.TOKEN });
12
15
  return;
13
16
  }
14
17
 
@@ -0,0 +1,9 @@
1
+ let authEndpoint = 'https://test.id.spsc.io/';
2
+ let primaryOrgID = '209098808305562446811265341033453015703'; // SPS Commerce org id on test env
3
+
4
+ if (process.env.URL && process.env.URL.toLowerCase().includes('prod')) {
5
+ authEndpoint = 'https://id.spsc.io/';
6
+ primaryOrgID = '157627141979612915626333577105111854232'; // SPS Commerce org id on prod env
7
+ }
8
+
9
+ export { authEndpoint, primaryOrgID };
@@ -1,5 +1,5 @@
1
- const path = require('path');
2
- const fs = require('fs');
1
+ import fs from "fs";
2
+ import path from "path";
3
3
 
4
4
  let access: any;
5
5
 
@@ -13,7 +13,7 @@ export async function enableTracer(): Promise<void> {
13
13
 
14
14
  export async function disableTracer(): Promise<void> {
15
15
  const testName = expect.getState().currentTestName.replace(/ /g, "");
16
- const tracePathPerSuite = path.join(process.cwd(), 'results/traces');
16
+ const tracePathPerSuite = path.join(process.cwd(), 'results-e2e/traces');
17
17
 
18
18
  // Stop tracing and write it to file per test
19
19
  try {
@@ -28,7 +28,7 @@ export async function disableTracer(): Promise<void> {
28
28
 
29
29
  export async function enableLogger(): Promise<void> {
30
30
  const testName = expect.getState().currentTestName.replace(/ /g, "");
31
- const logPathPerSuite = path.join(process.cwd(), 'results/logs');
31
+ const logPathPerSuite = path.join(process.cwd(), 'results-e2e/logs');
32
32
 
33
33
  // Create folder per suite for error files
34
34
  await fs.mkdirSync(logPathPerSuite, { recursive: true });
@@ -37,11 +37,6 @@ export async function enableLogger(): Promise<void> {
37
37
  access = fs.createWriteStream(`${logPathPerSuite}/${testName}.log`)
38
38
  process.stdout.write = process.stderr.write = access.write.bind(access);
39
39
 
40
- // Write the GitHub link of the currently running test to the beginning of each log file
41
- const testNameGH = testName.split('_').slice(2).join('_');
42
- // Set up your repository url here
43
- console.log(`https://github.com/SPSCommerce/[your_GH_repo_name]/search?q=${testNameGH}`);
44
-
45
40
  // Listen for errors
46
41
  page
47
42
  .on('console', async message => {
@@ -18,7 +18,6 @@ class JestEnvironmentFailFast extends ParentEnvironment {
18
18
  await super.teardown();
19
19
  if (this.videos.length !== 0) {
20
20
  const { recordVideo } = pwConfig.contextOptions;
21
- console.log(this.videos)
22
21
  const anyTestFailed = this.videos.find(element => element.success !== true);
23
22
  if (anyTestFailed) {
24
23
  const newVideoName = `${recordVideo.dir}/${anyTestFailed.testSuiteName}.webm`;
@@ -37,8 +36,8 @@ class JestEnvironmentFailFast extends ParentEnvironment {
37
36
  // TEST STARTED
38
37
  // Delete already existing screenshot/trace file before test starts if it ran twice due to jest.retryTimes()
39
38
  if (event.name === 'test_start') {
40
- const traceFilePerTest = path.join(process.cwd(), `results/traces/${event.test.parent.name}${event.test.name}.zip`);
41
- const screenFilePerTest = path.join(process.cwd(), `results/screenshots/${event.test.parent.name}${event.test.name}.png`);
39
+ const traceFilePerTest = path.join(process.cwd(), `results-e2e/traces/${event.test.parent.name}${event.test.name}.zip`);
40
+ const screenFilePerTest = path.join(process.cwd(), `results-e2e/screenshots/${event.test.parent.name}${event.test.name}.png`);
42
41
  if (fs.existsSync(traceFilePerTest)) {
43
42
  fs.unlinkSync(traceFilePerTest);
44
43
  }
@@ -55,7 +54,7 @@ class JestEnvironmentFailFast extends ParentEnvironment {
55
54
  if (state.currentlyRunningTest) {
56
55
  await this.global.page.screenshot({
57
56
  fullPage: true,
58
- path: process.cwd() + `/results/screenshots/${event.test.parent.name}${event.test.name}.png`,
57
+ path: process.cwd() + `/results-e2e/screenshots/${event.test.parent.name}${event.test.name}.png`,
59
58
  });
60
59
  }
61
60
  }
@@ -63,7 +62,7 @@ class JestEnvironmentFailFast extends ParentEnvironment {
63
62
  // TEST FINISHED SUCCESSFULLY
64
63
  // Delete trace file for each succeeded test
65
64
  if (event.name === 'test_done' && event.test.errors.length === 0) {
66
- const traceFilePerTest = path.join(process.cwd(), `results/traces/${event.test.parent.name}${event.test.name}.zip`);
65
+ const traceFilePerTest = path.join(process.cwd(), `results-e2e/traces/${event.test.parent.name}${event.test.name}.zip`);
67
66
  if (fs.existsSync(traceFilePerTest)) {
68
67
  fs.unlinkSync(traceFilePerTest);
69
68
  }
@@ -81,8 +80,8 @@ class JestEnvironmentFailFast extends ParentEnvironment {
81
80
 
82
81
  // TEST FINISHES AND FAILS
83
82
  // Skip all further tests in the suite (all tests are related to each other) if
84
- // - any test is failed AND suite name includes 'long_scenario'
85
- if (this.failedTest && event.name === 'test_start' && event.test.parent.name.includes('long_scenario')) {
83
+ // - any test is failed AND suite name includes 'FailFast'
84
+ if (this.failedTest && event.name === 'test_start' && event.test.parent.name.includes('FailFast')) {
86
85
  event.test.mode = 'skip';
87
86
  }
88
87
  }
@@ -1,3 +1,8 @@
1
+ const customViewport = {
2
+ width: 1920,
3
+ height: 1080,
4
+ }
5
+
1
6
  let runModeBrowser = [];
2
7
 
3
8
  // Run in chromium browser by default (if process.env.BROWSER is not specified)
@@ -19,13 +24,10 @@ if (!process.env.BROWSER) {
19
24
  case 'webkit':
20
25
  runModeBrowser.push('webkit');
21
26
  break;
22
- // Pay attention: browserName is 'chromium' for Edge browser (mentioned in describe)
23
- // https://support.microsoft.com/en-us/microsoft-edge/download-the-new-microsoft-edge-based-on-chromium-0f4a3dd7-55df-60f5-739f-00010dba52cf
24
27
  case 'edge':
25
28
  case 'msedge':
26
- // Channel is 'msedge-dev' since only it is available in linux
27
- // https://github.com/microsoft/playwright/blob/07fb81a4efdcc4c2169a61a16b513b9d91be6c49/src/server/chromium/findChromiumChannel.ts#L38
28
29
  runModeBrowser.push({ displayName: 'edge', name: 'chromium', launchOptions: { channel: 'msedge-dev' } });
30
+ // https://github.com/microsoft/playwright/blob/07fb81a4efdcc4c2169a61a16b513b9d91be6c49/src/server/chromium/findChromiumChannel.ts#L38
29
31
  break;
30
32
  default:
31
33
  runModeBrowser.push('chromium');
@@ -38,22 +40,27 @@ if (!process.env.BROWSER) {
38
40
  // Run in headless mode by default (if process.env.HEADLESS is not specified)
39
41
  const runModeHead = !process.env.HEADLESS || process.env.HEADLESS === 'true' ? true : false;
40
42
 
43
+ // Apply customViewport while running in headless mode
44
+ const runModeViewport = runModeHead ? customViewport : null;
45
+
46
+ let runModeArgs = [];
47
+
48
+ if (runModeBrowser === ['chromium'] && runModeHead === false) {
49
+ runModeArgs = ['--window-size=1920,1080'];
50
+ }
51
+
41
52
  module.exports = {
42
53
  exitOnPageError: false,
43
54
  launchOptions: {
55
+ args: runModeArgs,
44
56
  headless: runModeHead,
45
57
  slowMo: process.env.SLOWMO ? +process.env.SLOWMO : 0,
46
58
  },
47
59
  launchType: "LAUNCH",
48
60
  contextOptions: {
49
61
  acceptDownloads: true,
50
- viewport: {
51
- width: 1920,
52
- height: 1080
53
- },
54
- recordVideo: process.env.VIDEO && {
55
- dir: process.cwd() + '/results/video',
56
- }
62
+ viewport: runModeViewport,
63
+ ...(process.env.VIDEO && {recordVideo: { dir: process.cwd() + '/results/video' } }),
57
64
  },
58
65
  browsers: runModeBrowser
59
66
  }
package/jest.auth.js CHANGED
@@ -1,37 +1,5 @@
1
- const { authFile, authRequest } = require('./env.config');
2
- const fetch = require('node-fetch');
3
- const fs = require('fs');
1
+ const { getAuthToken } = require('./helpers/apiCalls');
4
2
 
5
3
  module.exports = async () => {
6
- // Run the script, if auth[Env].json file does not exist yet
7
- if (!fs.existsSync(authFile)) {
8
- // Send auth request, if process.env.USER_EMAIL and process.env.USER_PASSWORD are set up
9
- if (process.env.USER_EMAIL) {
10
- const rawResponse = await fetch(authRequest, {
11
- method: 'POST',
12
- body: JSON.stringify({
13
- username: process.env.USER_EMAIL,
14
- password: process.env.USER_PASSWORD,
15
- client_id: "595",
16
- grant_type: "password"
17
- }),
18
- headers: {
19
- 'Content-Type': 'application/json'
20
- }
21
- });
22
- const requestContent = await rawResponse.json();
23
- // Save token into the newly created auth[Env].json file, if token value exists in the response
24
- if (requestContent.access_token) {
25
- fs.copyFileSync('./auth.json', authFile)
26
- let fileContent = JSON.parse(fs.readFileSync(authFile, 'utf8'));
27
- fileContent.access_token = requestContent.access_token;
28
- fs.writeFileSync(authFile, JSON.stringify(fileContent));
29
- } else {
30
- console.log(`Auth request response: ${JSON.stringify(requestContent)}`)
31
- throw new Error(`Something went wrong while invoking auth state.`);
32
- }
33
- } else {
34
- throw new Error(`Set up process.env.USER_EMAIL and process.env.USER_PASSWORD (run "source .credentials.sh" locally).`);
35
- }
36
- }
4
+ process.env.TOKEN = await getAuthToken();
37
5
  }
package/jest.config.js CHANGED
@@ -10,13 +10,14 @@ module.exports = {
10
10
  includeShortConsoleOutput: true,
11
11
  classNameTemplate: "{classname}",
12
12
  titleTemplate: "{classname}{title}",
13
- outputDirectory: "results/jest-junit-report",
13
+ outputDirectory: "results-e2e/jest-junit-report",
14
14
  outputName: process.env.SUITE ? `TEST-${process.env.SUITE}.xml` : `TEST-result.xml`
15
15
  },
16
16
  ],
17
17
  ],
18
18
  setupFilesAfterEnv: [
19
19
  "./jest.setup.js",
20
+ "jest-extended/all",
20
21
  // https://github.com/mattphillips/jest-expect-message/issues/39
21
22
  // "jest-expect-message",
22
23
  "@alex_neo/jest-expect-message"
@@ -27,5 +28,8 @@ module.exports = {
27
28
  transform: {
28
29
  "^.+\\.(ts)$": "ts-jest",
29
30
  },
31
+ transformIgnorePatterns: [
32
+ "/node_modules/?!(@spscommerce/e2e-core)"
33
+ ],
30
34
  verbose: true,
31
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spscommerce/e2e-core",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "An automation test framework which is based on playwright, jest and ts",
5
5
  "scripts": {
6
6
  "test": "jest",
@@ -25,6 +25,7 @@
25
25
  "@alex_neo/jest-expect-message": "^1.0.5",
26
26
  "@types/jest": "^26.0.24",
27
27
  "@types/jest-expect-message": "^1.0.3",
28
+ "@types/node-fetch": "^3.0.3",
28
29
  "@typescript-eslint/eslint-plugin": "^4.33.0",
29
30
  "@typescript-eslint/parser": "^4.33.0",
30
31
  "eslint": "^7.32.0",
@@ -33,10 +34,12 @@
33
34
  "jest": "^27.3.1",
34
35
  "jest-circus": "^27.3.1",
35
36
  "jest-expect-message": "^1.0.2",
37
+ "jest-extended": "^1.2.0",
36
38
  "jest-junit": "^13.0.0",
37
39
  "jest-playwright-preset": "^1.7.0",
38
- "node-fetch": "^2.6.5",
40
+ "node-fetch": "^2.6.6",
39
41
  "playwright": "^1.16.1",
42
+ "playwright-core": "^1.17.1",
40
43
  "ts-jest": "^27.0.7",
41
44
  "typescript": "^4.4.4"
42
45
  }
package/auth.json DELETED
@@ -1 +0,0 @@
1
- {"access_token":"","orgHint":"[\"209098808305562446811265341033453015703\"]"}
package/env.config.ts DELETED
@@ -1,9 +0,0 @@
1
- let authFile = './authTest.json';
2
- let authRequest = 'https://test.id.spsc.io/identity/token/';
3
-
4
- if (process.env.ENV && process.env.ENV.toLowerCase() === 'prod') {
5
- authFile = './authProd.json';
6
- authRequest = 'https://id.spsc.io/identity/token/';
7
- }
8
-
9
- export { authFile, authRequest }