Set up end to end tests (#121)
* initial e2e test setup with jest and puppeteer * Update package-lock.json * WIP e2e setup * finish setting up end to end tests * extend open addon time * build local version and change background color * update pink image snapshot * hide no "done" callback jest lint error * first draft of integration test CI automation * add .env file during CI * add puppeteer and update .env * try mac-os * try separate build step * add local deploy step * extend time * print body text * set user agent * rearrange console logs * remove console logs * console page * try loading in own window * install mkcert in CI * fix indentation * test local body text * bump time * revert to loading in add on * bump time * print page body * add stealth plugin and update puppeteer * update jest and jest-puppeteer versions to latest * use custom jest-puppeteer environment allows connecting to Google account using stealth plugin * update threshold * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * test console * switch to clasp 2.3.1 * increase timeout * Update integration-tests.yaml * Update integration-tests.yaml * Update package.json * try mac os 10.15 * Update integration-tests.yaml * run node 12,14 * Update global-teardown.js * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * sudo mkcert * Update integration-tests.yaml * try running not in bash * try windows 2016 * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * Update open-addon.js * Update open-addon.js * Update open-addon.js * Update open-addon.js * Update open-addon.js * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * add timeout * test recovery email * bypass account recovery page * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * test New-SelfSignedCertificate * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * add powershell script * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * Update integration-tests.yaml * test script * Update integration-tests.yaml * add generate-cert.ps1 * restructure test files * Update generate-cert.ps1 * bump failure threshold * fix install mkcert * remove consoles * Update package.json * Update integration-tests.yaml * try forceExit flag * test exec in global setup * restructure * Update integration-tests.yaml * remove ?. null check * add 4 OSs: macos-10.15, macos-latest, windows-2019, windows-latest * test mac 10.15 * Update integration-tests.yaml * use all OSs * -Keyexportpolicy Exportable * Update generate-cert.ps1 * Update integration-tests.yaml * update os * -KeyExportPolicy 'Exportable' * Update integration-tests.yaml * update test readme * Update integration-tests.yaml * add non-extended integration test * add non-extended snapshots * update snapshot names * change name * add basic integration tests pipeline file * Update local-development.test.js * update os's * test s3 image reporter * allowSizeMismatch: true * update test local url to localhost:3000/dialog-demo-bootstrap.html * try medium font render hinting * Update jest-puppeteer.config.js * update screenshots * check resolution * open page when wds is ready * change screen resolution * change to 2560 1440 resolution * test 790 width * change back to 800 * test all mac versions * rename container * skip s3 image reporting if missing env variables * try windows * add OS specific tests * fix image names * Update image-reporter.js * Update package.json * upload windows * Update image-reporter.js * test new font * try all OSs * remove OS-specific test setup * update extended snapshots * rename tests * Update integration-tests-extended.yaml * Update integration-tests-basic.yaml * extend timeout * Update jest.config.js * Update README.md * Update local-development.test.js * revert to 0.04 threshold * add comment * remove push trigger for basic test * extend timeout
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
name: Local integration tests - Basic Version
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
basic-integration-test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-10.15, macos-latest, windows-latest]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
node-version: [12, 14, 16]
|
||||
timeout-minutes: 8
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install packages [npm ci]
|
||||
run: npm ci
|
||||
- name: Allow installing cert in Mac OS Big Sur
|
||||
run: sudo security authorizationdb write com.apple.trust-settings.admin allow
|
||||
if: matrix.os == 'macOS-latest'
|
||||
- name: Install mkcert
|
||||
run: brew install mkcert
|
||||
if: runner.os == 'MacOS'
|
||||
- name: Run mkcert setup [mkcert -install]
|
||||
run: mkcert -install
|
||||
if: runner.os == 'MacOS'
|
||||
- name: Install https cert [npm setup:https]
|
||||
run: npm run setup:https
|
||||
if: runner.os == 'MacOS'
|
||||
- run: |
|
||||
mkdir certs
|
||||
.\test\generate-cert.ps1
|
||||
shell: pwsh
|
||||
if: runner.os == 'Windows'
|
||||
- name: Run integration tests
|
||||
run: npm run test:integration
|
||||
shell: bash
|
||||
@@ -0,0 +1,72 @@
|
||||
name: Local integration tests - Extended Version
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
extended-integration-test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-10.15, macos-latest, windows-latest]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
node-version: [12, 14, 16]
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Install packages [npm ci]
|
||||
run: npm ci
|
||||
- name: Allow installing cert in Mac OS Big Sur
|
||||
run: sudo security authorizationdb write com.apple.trust-settings.admin allow
|
||||
if: matrix.os == 'macOS-latest'
|
||||
- name: Install mkcert
|
||||
run: brew install mkcert
|
||||
if: runner.os == 'MacOS'
|
||||
- name: Run mkcert setup [mkcert -install]
|
||||
run: mkcert -install
|
||||
if: runner.os == 'MacOS'
|
||||
- name: Install https cert [npm setup:https]
|
||||
run: npm run setup:https
|
||||
if: runner.os == 'MacOS'
|
||||
- run: |
|
||||
mkdir certs
|
||||
.\test\generate-cert.ps1
|
||||
shell: pwsh
|
||||
if: runner.os == 'Windows'
|
||||
- name: Add .clasprc.json to home folder
|
||||
run: echo "$DOT_CLASPRC" > $HOME/.clasprc.json
|
||||
shell: bash
|
||||
env:
|
||||
DOT_CLASPRC: ${{ secrets.DOT_CLASPRC }}
|
||||
- name: Add .clasp.json to project directory
|
||||
run: echo "$DOT_CLASP" > .clasp.json
|
||||
shell: bash
|
||||
env:
|
||||
DOT_CLASP: ${{ secrets.DOT_CLASP }}
|
||||
- name: Add environment variables to .env file
|
||||
run: |
|
||||
echo "EMAIL=$TEST_ACCOUNT_EMAIL" > .env
|
||||
echo "TEST_RECOVERY_EMAIL=$TEST_RECOVERY_EMAIL" >> .env
|
||||
echo "PASSWORD=$TEST_ACCOUNT_PASSWORD" >> .env
|
||||
echo "SHEET_URL=$TEST_SPREADSHEET_URL" >> .env
|
||||
echo "S3_BUCKET_NAME=$S3_BUCKET_NAME" >> .env
|
||||
echo "AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY" >> .env
|
||||
echo "AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID" >> .env
|
||||
shell: bash
|
||||
env:
|
||||
TEST_ACCOUNT_EMAIL: ${{ secrets.TEST_ACCOUNT_EMAIL }}
|
||||
TEST_RECOVERY_EMAIL: ${{ secrets.TEST_RECOVERY_EMAIL }}
|
||||
TEST_ACCOUNT_PASSWORD: ${{ secrets.TEST_ACCOUNT_PASSWORD }}
|
||||
TEST_SPREADSHEET_URL: ${{ secrets.TEST_SPREADSHEET_URL }}
|
||||
S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
- name: Build and deploy dev setup [npm run deploy:dev]
|
||||
run: npm run deploy:dev
|
||||
- name: Run integration tests
|
||||
run: npm run test:integration:extended
|
||||
shell: bash
|
||||
@@ -14,3 +14,6 @@ dist/
|
||||
|
||||
# mac
|
||||
.DS_Store
|
||||
|
||||
#secret
|
||||
.env
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
globalSetup: './test/global-setup.js',
|
||||
globalTeardown: './test/global-teardown.js',
|
||||
testEnvironment: './test/puppeteer-environment.js',
|
||||
reporters: ['default', '<rootDir>/test/utils/image-reporter.js'],
|
||||
};
|
||||
Generated
+12736
-4280
File diff suppressed because it is too large
Load Diff
+13
-2
@@ -7,7 +7,8 @@
|
||||
"url": "https://github.com/enuchi/React-Google-Apps-Script.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "test",
|
||||
"test:integration": "jest --forceExit test/local-development.test",
|
||||
"test:integration:extended": "cross-env IS_EXTENDED=1 jest --forceExit test/local-development.test",
|
||||
"login": "clasp login",
|
||||
"setup": "rimraf .clasp.json && clasp create --type sheets --title \"My React Project\" --rootDir ./dist",
|
||||
"open": "clasp open --addon",
|
||||
@@ -50,7 +51,11 @@
|
||||
"@babel/polyfill": "^7.10.4",
|
||||
"@babel/preset-env": "^7.11.0",
|
||||
"@babel/preset-react": "^7.10.4",
|
||||
"@google/clasp": "^2.4.0",
|
||||
"@google/clasp": "^2.3.1",
|
||||
"@types/expect-puppeteer": "^4.4.6",
|
||||
"@types/jest-environment-puppeteer": "^4.4.1",
|
||||
"@types/puppeteer": "^5.4.4",
|
||||
"aws-sdk": "^2.1092.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-add-module-exports": "^1.0.2",
|
||||
@@ -69,6 +74,7 @@
|
||||
"eslint-plugin-babel": "^5.3.1",
|
||||
"eslint-plugin-googleappsscript": "^1.0.3",
|
||||
"eslint-plugin-import": "^2.22.0",
|
||||
"eslint-plugin-jest": "^24.3.6",
|
||||
"eslint-plugin-jsx-a11y": "^6.3.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^3.1.4",
|
||||
@@ -81,9 +87,14 @@
|
||||
"google-apps-script-webpack-dev-server": "^1.0.0",
|
||||
"html-webpack-inline-source-plugin": "0.0.10",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"jest": "^27.5.1",
|
||||
"jest-image-snapshot": "^4.5.1",
|
||||
"mkdirp": "^1.0.4",
|
||||
"module-to-cdn": "^3.1.5",
|
||||
"prettier": "^1.19.1",
|
||||
"puppeteer": "^13.4.1",
|
||||
"puppeteer-extra": "^3.2.3",
|
||||
"puppeteer-extra-plugin-stealth": "^2.9.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"style-loader": "^1.2.1",
|
||||
"tern": "^0.24.3",
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/* needed to make consistent test snapshots across OSs */
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif !important;
|
||||
}
|
||||
|
||||
/*
|
||||
CSSTransitionGroup styling
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "babel-eslint",
|
||||
"extends": [
|
||||
"airbnb-base",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:jest/recommended"
|
||||
],
|
||||
"plugins": ["babel", "prettier"],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"jest": true
|
||||
},
|
||||
"globals": {
|
||||
"page": true,
|
||||
"browser": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 9,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"jest/no-done-callback": "off",
|
||||
"prettier/prettier": "error",
|
||||
"camelcase": "warn",
|
||||
"import/prefer-default-export": "warn",
|
||||
"import/no-extraneous-dependencies": "warn",
|
||||
"jest/expect-expect": "off",
|
||||
"no-new": "off",
|
||||
"no-underscore-dangle": "off"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# Running tests
|
||||
|
||||
Integration tests here are used to ensure the existing local development setup works across different platforms, and that new features do not break the functionality of the local development environment.
|
||||
|
||||
The "extended" local development integration test builds and deploys the local development setup needed to use hot reload functionality. It deploys the local setup to a spreadsheet inside a test Google account, runs a local development server, and checks that changes to the local files are reflected inside the spreadsheet's add-on, similar to how local development is intended to be used.
|
||||
|
||||
Jest is used to run the tests and compare images of the changes, and Puppeteer is used to control the browser. A test account and test spreadsheet need to be created and credentials need to be passed in through environment variables in order for this "extended" version to work. Example `.env` file variables are listed below. Since secret values cannot be used in PRs opened from forks, a "basic" integration test just loads the local development setup locally, instead of deploying to Google Sheets.
|
||||
|
||||
## Extended integration test
|
||||
|
||||
In order to test the extended integration test locally you will need to create or use a test Google account. It's recommended you create an account solely for this purpose rather than using an existing account. You'll need to create a new project and include the following variables in the `.env` file in the root:
|
||||
|
||||
```bash
|
||||
SHEET_URL = https://docs.google.com/spreadsheets/d/****************/edit#gid=0
|
||||
EMAIL = myisolatedtestaccount@gmail.com
|
||||
PASSWORD = testaccountpassword
|
||||
TEST_RECOVERY_EMAIL = myisolatedtestaccountsrecoveryemail@gmail.com
|
||||
|
||||
# For image reporting only
|
||||
S3_BUCKET_NAME = my-bucket-name
|
||||
AWS_ACCESS_KEY_ID = A35D************
|
||||
AWS_SECRET_ACCESS_KEY = slkf***********/234jdh**********
|
||||
```
|
||||
|
||||
Make sure that you have followed the instructions to install local certs (see main README). The test will use Puppeteer to open the spreadsheet URL and log in to your account using your email and password provided. In case it doesn't recognize you it will ask for this account's recovery email -- you'll need to include this in the `.env` file as well.
|
||||
|
||||
### Running the extended tests in the pipeline
|
||||
|
||||
To run the extended integration test in a pipeline using Github Actions, reference the .github/workflows directory. You'll need to add some additional environment variables through the repo secrets settings in order to get the full flow working:
|
||||
|
||||
DOT_CLASPRC - the `.clasprs.json` file from user directory containing clasp login credentials
|
||||
DOT_CLASP - the `.clasp.json` file from this repo matching the test spreadsheet
|
||||
TEST_ACCOUNT_EMAIL, TEST_RECOVERY_EMAIL, TEST_ACCOUNT_PASSWORD, TEST_SPREADSHEET_URL - see above variables
|
||||
|
||||
# Additional notes
|
||||
|
||||
### Stealth login
|
||||
`puppeteer-extra` and `puppeteer-extra-plugin-stealth` are used to help log in to a Google account in the pipeline for the "extended" integration test. They are not easily compatible with the `jest-puppeteer` package, so a custom integration is used that is documented on `jest`'s site [here](jestjs.io/docs/puppeteer#custom-example-without-jest-puppeteer-preset).
|
||||
|
||||
### Generating certs in the pipeline
|
||||
There are permissions issues running `mkcert -install` in Windows runners, so a powershell script in `./generate-cert.ps1` is used to generate the certs and enable https.
|
||||
|
||||
### Jest open handles
|
||||
There are issues with `jest` keeping handles open after all tests finish on Windows runners, so `jest --forceExit` is used to resolve them, although this flag may not be needed on certain platforms (like Mac OS).
|
||||
|
||||
### Jest Image Snapshots
|
||||
`jest-image-snapshot` is used to compare browser snapshots and detect discrepancies or regressions. Thresholds have been adjusted to allow for some variation across different platforms due to differences in font rendering and color profiles.
|
||||
|
||||
If a snapshot fails, a diff image is created in the __diff_output__ directory, showing the comparison. If it fails in the pipeline it may be hard to see this diff. An Image Reporter class is used to upload diff images to an S3 bucket from the pipeline. This can be modified to use any image store. If used, make sure it is enabled in `jest.config.js` and that the necessary environment variables (AWS keys, bucket name, etc.) are added to `.env` file (see above section).
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
@@ -0,0 +1,40 @@
|
||||
# reference code: https://stackoverflow.com/questions/70226493/webpack-dev-server-and-https-this-site-can-t-be-reached
|
||||
# reference issue: https://github.com/FiloSottile/mkcert/issues/286
|
||||
|
||||
$dnsName = "localhost"
|
||||
$expiry = [DateTime]::Now.AddYears(1);
|
||||
$repoRoot = Split-Path $PSScriptRoot
|
||||
$certsDir = "$repoRoot\certs";
|
||||
$fileName = "cert.pfx";
|
||||
$passwordText = "abc123";
|
||||
$name = "ReactApp";
|
||||
|
||||
Write-Host "Creating cert directly into CurrentUser\My store"
|
||||
|
||||
$certificate = New-SelfSignedCertificate `
|
||||
-KeyExportPolicy 'Exportable' `
|
||||
-CertStoreLocation Cert:\CurrentUser\My `
|
||||
-Subject $name `
|
||||
-FriendlyName $name `
|
||||
-DnsName $dnsName `
|
||||
-NotAfter $expiry
|
||||
|
||||
$certFile = Join-Path $certsDir $fileName
|
||||
|
||||
Write-Host "Exporting certificate to $certFile"
|
||||
|
||||
$password = ConvertTo-SecureString `
|
||||
-String $passwordText `
|
||||
-Force -AsPlainText
|
||||
|
||||
Export-PfxCertificate `
|
||||
-Cert $certificate `
|
||||
-FilePath $certFile `
|
||||
-Password $password | Out-Null
|
||||
|
||||
Write-Host "Importing $certFile to CurrentUser\Root store for immediate system wide trust"
|
||||
|
||||
Import-PfxCertificate `
|
||||
-FilePath $certFile `
|
||||
-CertStoreLocation Cert:\LocalMachine\Root `
|
||||
-Password $password | Out-Null
|
||||
@@ -0,0 +1,26 @@
|
||||
// Use custom jest puppeteer preset as described here:
|
||||
// jestjs.io/docs/puppeteer#custom-example-without-jest-puppeteer-preset
|
||||
// This allows using stealth mode.
|
||||
|
||||
const { mkdir, writeFile } = require('fs').promises;
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const puppeteer = require('puppeteer-extra');
|
||||
|
||||
// add stealth plugin and use defaults (all evasion techniques)
|
||||
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
|
||||
|
||||
const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
|
||||
const jestPuppeteerConfig = require('./jest-puppeteer.config');
|
||||
|
||||
module.exports = async function() {
|
||||
puppeteer.use(StealthPlugin());
|
||||
const browser = await puppeteer.launch(jestPuppeteerConfig.launch);
|
||||
// store the browser instance so we can teardown it later
|
||||
// this global is only available in the teardown but not in TestEnvironments
|
||||
global.__BROWSER_GLOBAL__ = browser;
|
||||
|
||||
// use the file system to expose the wsEndpoint for TestEnvironments
|
||||
await mkdir(DIR, { recursive: true });
|
||||
await writeFile(path.join(DIR, 'wsEndpoint'), browser.wsEndpoint());
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
// Use custom jest puppeteer preset as described here:
|
||||
// jestjs.io/docs/puppeteer#custom-example-without-jest-puppeteer-preset
|
||||
// This allows using stealth mode.
|
||||
|
||||
const fs = require('fs').promises;
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
|
||||
module.exports = async function() {
|
||||
// close the browser instance
|
||||
await global.__BROWSER_GLOBAL__.close();
|
||||
|
||||
// clean-up the wsEndpoint file
|
||||
await fs.rmdir(DIR, { recursive: true, force: true });
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
launch: {
|
||||
headless: false,
|
||||
product: 'chrome',
|
||||
args: [
|
||||
'--force-color-profile=generic-rgb',
|
||||
'--font-render-hinting=none',
|
||||
'--disable-font-subpixel-positioning',
|
||||
'--enable-font-antialiasing',
|
||||
'--disable-gpu',
|
||||
],
|
||||
},
|
||||
browserContext: 'default',
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { exec } = require('child_process');
|
||||
const { configureToMatchImageSnapshot } = require('jest-image-snapshot');
|
||||
const { openAddon } = require('./utils/open-addon');
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
const isExtended = process.env.IS_EXTENDED == true;
|
||||
|
||||
const toMatchImageSnapshot = configureToMatchImageSnapshot({
|
||||
failureThreshold: 0.04,
|
||||
failureThresholdType: 'percent',
|
||||
customDiffConfig: {
|
||||
threshold: 0.1,
|
||||
},
|
||||
blur: 2,
|
||||
allowSizeMismatch: true,
|
||||
});
|
||||
expect.extend({ toMatchImageSnapshot });
|
||||
jest.setTimeout(120000);
|
||||
|
||||
const srcTestFile = path.join(
|
||||
__dirname,
|
||||
'../src/client/dialog-demo-bootstrap/components/SheetEditor.jsx'
|
||||
);
|
||||
|
||||
const webpackDevServerReady = async process => {
|
||||
return new Promise(resolve => {
|
||||
process.stdout.on('data', data => {
|
||||
if (data.includes('Compiled successfully.')) resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe(`Local setup ${isExtended ? '*extended*' : ''}`, () => {
|
||||
let page;
|
||||
let process;
|
||||
const containerSelector = isExtended ? '.script-app-dialog' : 'body';
|
||||
|
||||
beforeAll(async () => {
|
||||
process = exec('npm run serve');
|
||||
page = await global.__BROWSER_GLOBAL__.newPage();
|
||||
|
||||
await page.setViewport({
|
||||
width: 800,
|
||||
height: 800,
|
||||
deviceScaleFactor: 1,
|
||||
});
|
||||
|
||||
if (isExtended) {
|
||||
await openAddon(page);
|
||||
} else {
|
||||
await webpackDevServerReady(process);
|
||||
await page.goto('https://localhost:3000/dialog-demo-bootstrap.html');
|
||||
await page.waitForTimeout(3000);
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.kill();
|
||||
});
|
||||
|
||||
it('should load Bootstrap example', async () => {
|
||||
const container = await page.$(containerSelector);
|
||||
const image = await container.screenshot();
|
||||
await expect(image).toMatchImageSnapshot();
|
||||
});
|
||||
|
||||
it('should modify Bootstrap title example', async () => {
|
||||
const data = await fs.promises.readFile(srcTestFile, 'utf8');
|
||||
const result = data
|
||||
.replace(
|
||||
'<b>☀️ Bootstrap demo! ☀️</b>',
|
||||
'<b>☀️ This is modified text in local development ☀️</b>'
|
||||
)
|
||||
.replace(
|
||||
"{ padding: '3px', overflowX: 'hidden' }",
|
||||
"{ padding: '3px', overflowX: 'hidden', backgroundColor: 'black', color: 'white' }"
|
||||
);
|
||||
await fs.promises.writeFile(srcTestFile, result, 'utf8');
|
||||
await page.waitForTimeout(4000);
|
||||
const container = await page.$(containerSelector);
|
||||
const image = await container.screenshot();
|
||||
await expect(image).toMatchImageSnapshot();
|
||||
});
|
||||
|
||||
it('should modify Bootstrap title example back to original', async () => {
|
||||
const data = await fs.promises.readFile(srcTestFile, 'utf8');
|
||||
const result = data
|
||||
.replace(
|
||||
'<b>☀️ This is modified text in local development ☀️</b>',
|
||||
'<b>☀️ Bootstrap demo! ☀️</b>'
|
||||
)
|
||||
.replace(
|
||||
"{ padding: '3px', overflowX: 'hidden', backgroundColor: 'black', color: 'white' }",
|
||||
"{ padding: '3px', overflowX: 'hidden' }"
|
||||
);
|
||||
await fs.promises.writeFile(srcTestFile, result, 'utf8');
|
||||
await page.waitForTimeout(4000);
|
||||
const container = await page.$(containerSelector);
|
||||
const image = await container.screenshot();
|
||||
await expect(image).toMatchImageSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
// Use custom jest puppeteer preset as described here:
|
||||
// jestjs.io/docs/puppeteer#custom-example-without-jest-puppeteer-preset
|
||||
// This allows using stealth mode.
|
||||
|
||||
const { readFile } = require('fs').promises;
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const puppeteer = require('puppeteer');
|
||||
const NodeEnvironment = require('jest-environment-node');
|
||||
|
||||
const DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');
|
||||
|
||||
class PuppeteerEnvironment extends NodeEnvironment {
|
||||
constructor(config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
async setup() {
|
||||
await super.setup();
|
||||
// get the wsEndpoint
|
||||
const wsEndpoint = await readFile(path.join(DIR, 'wsEndpoint'), 'utf8');
|
||||
if (!wsEndpoint) {
|
||||
throw new Error('wsEndpoint not found');
|
||||
}
|
||||
|
||||
// connect to puppeteer
|
||||
this.global.__BROWSER_GLOBAL__ = await puppeteer.connect({
|
||||
browserWSEndpoint: wsEndpoint,
|
||||
});
|
||||
}
|
||||
|
||||
async teardown() {
|
||||
await super.teardown();
|
||||
}
|
||||
|
||||
getVmContext() {
|
||||
return super.getVmContext();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PuppeteerEnvironment;
|
||||
@@ -0,0 +1,74 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* Reference: https://github.com/americanexpress/jest-image-snapshot/blob/main/examples/image-reporter.js
|
||||
*
|
||||
* To enable this image reporter, add it to your `jest.config.js` "reporters" definition:
|
||||
* "reporters": [ "default", "<rootDir>/image-reporter.js" ]
|
||||
*
|
||||
* Image Reporter may not work with jest's --forceExit flag
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const AWS = require('aws-sdk/global');
|
||||
const S3 = require('aws-sdk/clients/s3'); // this is needed
|
||||
require('dotenv').config();
|
||||
|
||||
const UPLOAD_BUCKET = process.env.S3_BUCKET_NAME;
|
||||
|
||||
const s3 = new AWS.S3({ apiVersion: '2006-03-01' });
|
||||
|
||||
class ImageReporter {
|
||||
constructor(globalConfig, options) {
|
||||
this._globalConfig = globalConfig;
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
onTestResult(test, testResult, aggregateResults) {
|
||||
if (
|
||||
testResult.numFailingTests &&
|
||||
testResult.failureMessage.match(/different from snapshot/)
|
||||
) {
|
||||
if (
|
||||
!process.env.S3_BUCKET_NAME ||
|
||||
!process.env.AWS_SECRET_ACCESS_KEY ||
|
||||
!process.env.AWS_ACCESS_KEY_ID
|
||||
) {
|
||||
console.log(
|
||||
'Missing env variables. Skipping upload of image diff files.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const targetDirectories = [
|
||||
'./test/__image_snapshots__/',
|
||||
'./test/__image_snapshots__/__diff_output__/',
|
||||
];
|
||||
targetDirectories.forEach(targetDirectory => {
|
||||
fs.readdirSync(targetDirectory, { withFileTypes: true }).forEach(
|
||||
dirent => {
|
||||
if (!dirent.isFile()) return;
|
||||
const path = `images/${dirent.name}`;
|
||||
const params = {
|
||||
Body: fs.readFileSync(`${targetDirectory}/${dirent.name}`),
|
||||
Bucket: UPLOAD_BUCKET,
|
||||
Key: path,
|
||||
ContentType: 'image/png',
|
||||
};
|
||||
s3.putObject(params, err => {
|
||||
if (err) {
|
||||
console.log(err, err.stack);
|
||||
} else {
|
||||
console.log(
|
||||
`Uploaded file to https://${UPLOAD_BUCKET}.s3.amazonaws.com/${path}`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ImageReporter;
|
||||
@@ -0,0 +1,74 @@
|
||||
const openAddon = async page => {
|
||||
await page.goto(process.env.SHEET_URL);
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await page.click('a:nth-child(2)'); // click on signin button
|
||||
await page.waitForTimeout(3000);
|
||||
await page.type('input[name="identifier"]', process.env.EMAIL); // type email
|
||||
await page.click('#identifierNext'); // click "next" button
|
||||
await page.waitForTimeout(3000);
|
||||
await page.type('input[name="password"]', process.env.PASSWORD); // type pass
|
||||
await page.waitForTimeout(3000);
|
||||
await page.click('#passwordNext'); // click "next" button
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
if (
|
||||
await page.evaluate(
|
||||
() =>
|
||||
document.querySelector('h1#headingText') &&
|
||||
document.querySelector('h1#headingText').innerText.includes('erify')
|
||||
)
|
||||
) {
|
||||
try {
|
||||
await page.click('div[data-accountrecovery]');
|
||||
await page.waitForTimeout(6000);
|
||||
} catch {
|
||||
console.log('The "choose account recovery method" page isn\'t shown');
|
||||
}
|
||||
|
||||
await page.type(
|
||||
'input[name="knowledgePreregisteredEmailResponse"]',
|
||||
process.env.TEST_RECOVERY_EMAIL
|
||||
); // type recovery email
|
||||
await page.waitForTimeout(6000);
|
||||
await page.click('button'); // click "next" button
|
||||
await page.waitForTimeout(5000);
|
||||
}
|
||||
|
||||
await page.waitForTimeout(25000); // wait long enough for onopen to be called
|
||||
|
||||
// open new addon menubar item
|
||||
await page.evaluate(() => {
|
||||
const addOnMenuButton = document.querySelector(
|
||||
'div.menu-button.goog-control.goog-inline-block:nth-child(11)'
|
||||
);
|
||||
addOnMenuButton.dispatchEvent(
|
||||
new MouseEvent('mousedown', { bubbles: true })
|
||||
);
|
||||
addOnMenuButton.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
|
||||
});
|
||||
|
||||
await page.waitForTimeout(6000);
|
||||
|
||||
// open "bootstrap" menu item
|
||||
await page.evaluate(() => {
|
||||
const bootstrapMenuButton = document.querySelector(
|
||||
'div.goog-menu.goog-menu-vertical.apps-menu-hide-mnemonics:last-child > div:nth-child(2) > div'
|
||||
);
|
||||
bootstrapMenuButton.dispatchEvent(
|
||||
new MouseEvent('mousedown', { bubbles: true })
|
||||
);
|
||||
bootstrapMenuButton.dispatchEvent(
|
||||
new MouseEvent('mouseup', { bubbles: true })
|
||||
);
|
||||
bootstrapMenuButton.dispatchEvent(
|
||||
new MouseEvent('mousedown', { bubbles: true })
|
||||
);
|
||||
bootstrapMenuButton.dispatchEvent(
|
||||
new MouseEvent('mouseup', { bubbles: true })
|
||||
);
|
||||
});
|
||||
await page.waitForTimeout(10000);
|
||||
};
|
||||
|
||||
module.exports = { openAddon };
|
||||
@@ -66,6 +66,7 @@ const clientEntrypoints = [
|
||||
// see "npm run setup:https" script in package.json
|
||||
const keyPath = path.resolve(__dirname, './certs/key.pem');
|
||||
const certPath = path.resolve(__dirname, './certs/cert.pem');
|
||||
const pfxPath = path.resolve(__dirname, './certs/cert.pfx'); // if needed for Windows
|
||||
|
||||
/*********************************
|
||||
* Declare settings
|
||||
@@ -224,6 +225,16 @@ if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
|
||||
};
|
||||
}
|
||||
|
||||
// If mkcert -install cannot be used on Windows machines (in pipeline, for example), the
|
||||
// script at test/generate-cert.ps1 can be used to create a .pfx cert
|
||||
if (fs.existsSync(pfxPath)) {
|
||||
// use pfx file if it's found
|
||||
devServer.https = {
|
||||
pfx: fs.readFileSync(pfxPath),
|
||||
passphrase: 'abc123',
|
||||
};
|
||||
}
|
||||
|
||||
// webpack settings for the development client wrapper
|
||||
const devClientConfigs = clientEntrypoints.map(clientEntrypoint => {
|
||||
envVars.FILENAME = clientEntrypoint.filename;
|
||||
|
||||
Reference in New Issue
Block a user