Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ci): added continuous integration tests #4577

Merged
merged 21 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
107 changes: 107 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# This workflow will do the following when a pull request is made against the main branch:
# 1. Clone base repository (apexcharts/apexcharts.js), install npm packages, build samples, and then generate e2e snapshots
# 2. Clone head repository (the repository with the code in the pull request), install npm packages, build samples, get snapshots generated from the base repository, run tests, and then generate an apexcharts build
# Test coverage results, base repository snapshots, head repository snapshots and diffs, sample HTML, and the apexcharts build are all uploaded to the workflow artifacts
# The diffs, build, and coverage results get uploaded even if the test fails for manual inspection and to make debugging easier

name: Node.js CI

on:
pull_request:
branches: [ main ]

jobs:
build:
name: Test & Build
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
node-version: [18.x, 20.x, 22.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- name: Create base repository artifacts folder
run: mkdir -p ${{ runner.temp }}/artifacts/base-repository
- name: Create head repository artifacts folder
run: mkdir -p ${{ runner.temp }}/artifacts/head-repository

- name: Checkout base repository
uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.base.repo.full_name }}
ref: ${{ github.event.pull_request.base.ref }}
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install base repository's packages
run: npm ci
- name: Generate base repository's samples
run: npm run build:samples
- name: Copy Base Repository Samples To Artifacts Folder
run: cp -r samples/vanilla-js ${{ runner.temp }}/artifacts/base-repository/samples
- name: Generate base repository's e2e snapshots
run: npm run e2e:update
- name: Copy Base Repository e2e Snapshots To Artifacts Folder
run: cp -r tests/e2e/snapshots ${{ runner.temp }}/artifacts/base-repository/snapshots

- name: Checkout head repository
uses: actions/checkout@v4
- name: Install head repository's packages
run: npm ci
- name: Generate head repository's samples
run: npm run build:samples
- name: Copy Head Repository Samples To Artifacts Folder
run: cp -r samples/vanilla-js ${{ runner.temp }}/artifacts/head-repository/samples
- name: Delete snapshots folder
run: rm -r tests/e2e/snapshots
- name: Copy snapshots from base repository
run: cp -r ${{ runner.temp }}/artifacts/base-repository/snapshots tests/e2e
- name: Run tests
run: npm run test:ci
- name: Copy Head Repository Diffs To Artifacts Folder
if: '!cancelled()'
run: cp -r tests/e2e/diffs ${{ runner.temp }}/artifacts/head-repository/diffs
- name: Build apexcharts
if: '!cancelled()'
run: npm run build --if-present
- name: Copy Head Repository Build To Artifacts Folder
if: '!cancelled()'
run: cp -r dist ${{ runner.temp }}/artifacts/head-repository/build
- name: Copy Head Repository Test Coverage To Artifacts Folder
if: '!cancelled()'
run: cp -r coverage ${{ runner.temp }}/artifacts/head-repository/coverage
- name: Upload Artifacts
if: '!cancelled()'
uses: actions/upload-artifact@v4
with:
name: node${{ matrix.node-version }}${{ runner.os }}results
path: ${{ runner.temp }}/artifacts

test-reproducibility:
name: Test Reproducibility
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
node-version: [18.x, 20.x, 22.x]

steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install packages
run: npm ci
- name: Build samples
run: npm run build:samples
- name: Generate snapshots
run: npm run e2e:update
- name: Run tests
run: npm run test:ci

20 changes: 20 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Lint

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js 22.x
uses: actions/setup-node@v4
with:
node-version: 22.x
- run: npm ci
- name: Lint
run: npm run lint
32 changes: 0 additions & 32 deletions .github/workflows/node.js.yml

This file was deleted.

1 change: 1 addition & 0 deletions PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ Please delete options that are not relevant.
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] My branch is up to date with any changes from the main branch
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"test": "npm run e2e && npm run unit",
"test:ci": "npm run e2e:ci && npm run unit",
"unit": "jest tests/unit/",
"e2e": "node tests/e2e/samples.js test",
"e2e:update": "node tests/e2e/samples.js update",
"e2e:ci": "node tests/e2e/samples.js test:ci",
"build:samples": "node samples/source/index.js generate"
},
"dependencies": {
Expand Down Expand Up @@ -105,4 +107,4 @@
"visualizations",
"data"
]
}
}
63 changes: 52 additions & 11 deletions tests/e2e/samples.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ class TestError extends Error {
}
}

class MissingSnapshotError extends Error {
constructor(message) {
super(message)
}
}

async function processSample(page, sample, command) {
const relPath = `${sample.dirName}/${sample.fileName}`
const vanillaJsHtml = `${rootDir}/samples/vanilla-js/${relPath}.html`
Expand Down Expand Up @@ -145,7 +151,16 @@ async function processSample(page, sample, command) {
// Compare screenshot to the original and throw error on differences
const testImg = PNG.sync.read(testImgBuffer)
// BUG: copy if original image doesn't exist and report in test results?
const originalImg = PNG.sync.read(fs.readFileSync(originalImgPath))
let originalImg;
try {
originalImg = PNG.sync.read(fs.readFileSync(originalImgPath))
} catch (e) {
if (e.code === 'ENOENT') {
//The file could not be found so throw a MissingSnapshotError
throw new MissingSnapshotError(relPath)
}
throw e
}
const { width, height } = testImg
const diffImg = new PNG({ width, height })

Expand Down Expand Up @@ -219,7 +234,7 @@ async function updateBundle(config) {
}
}

async function processSamples(command, paths) {
async function processSamples(command, paths, isCI) {
const startTime = Date.now()

await updateBundle(builds['web-umd-dev'])
Expand All @@ -241,6 +256,7 @@ async function processSamples(command, paths) {

let numCompleted = 0
const failedTests = [] // {path, error}
const testsMissingSnapshots = [] // 'pathForSnapshot'

// Build a list of samples to process
let samples = extractSampleInfo()
Expand Down Expand Up @@ -278,10 +294,14 @@ async function processSamples(command, paths) {
try {
await processSample(page, sample, command)
} catch (e) {
failedTests.push({
path: `${sample.dirName}/${sample.fileName}`,
error: e,
})
if (e instanceof MissingSnapshotError) {
testsMissingSnapshots.push(e.message)
} else {
failedTests.push({
path: `${sample.dirName}/${sample.fileName}`,
error: e,
})
}
}
numCompleted++
if (!process.stdout.isTTY) {
Expand Down Expand Up @@ -310,6 +330,13 @@ async function processSamples(command, paths) {
chalk.green.bold(`${samples.length} tests completed in ${duration} sec.`)
)

if (testsMissingSnapshots.length > 0) {
console.log(chalk.yellow.bold(`${testsMissingSnapshots.length} tests were missing snapshots to compare against. Those tests are:`))
for (const testMissingSnapshot of testsMissingSnapshots) {
console.log(chalk.yellow.bold(`${testMissingSnapshot}\n`))
}
}

if (failedTests.length > 0) {
console.log(chalk.red.bold(`${failedTests.length} tests failed`))
}
Expand Down Expand Up @@ -340,18 +367,32 @@ async function processSamples(command, paths) {
throw new Error('Code coverage report failed to generate')
}
}

if (failedTests.length > 0 && isCI) {
//Exit with error code to fail CI if a test failed
process.exit(1)
}
}

// Run as 'node samples.js <command> <path1> <path2> ...'
// Supports two commands:
// Supports three commands:
// - 'test' for running e2e tests
// - 'test:ci' for running e2e tests in CI - 'test:ci' exits with status code 1 if a test fails, while 'test' always exits with status code 0
// - 'update' for updating samples screenshots used for e2e tests comparison
// Path options have the format 'bar/basic-bar'. Paths are optional for 'test' command.
// For 'update' command 'all' path can be used to update all screenshots.
const command = process.argv[2]
if (['update', 'test'].includes(command)) {
processSamples(command, process.argv.slice(3))
.catch((e) => console.log(e))
const commandInput = process.argv[2]
if (['update', 'test', 'test:ci'].includes(commandInput)) {
const isCI = commandInput === 'test:ci'
const command = isCI ? 'test' : commandInput
processSamples(command, process.argv.slice(3), isCI)
.catch((e) => {
console.error(e)
if (isCI) {
//Exit with error code to fail CI if something failed
process.exit(1)
}
})
.then(() => {
if (browser) {
return browser.close()
Expand Down