Skip to content

Commit

Permalink
chore(tests) add test coverage
Browse files Browse the repository at this point in the history
Signed-off-by: David Edler <[email protected]>
  • Loading branch information
edlerd committed Feb 21, 2024
1 parent 0368ad1 commit 0427e00
Show file tree
Hide file tree
Showing 19 changed files with 1,519 additions and 608 deletions.
135 changes: 135 additions & 0 deletions .github/workflows/coverage.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
name: Test coverage
on:
push:
branches:
- main

jobs:
e2e-coverage:
name: e2e-test-coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Dotrun
run: |
sudo pip3 install dotrun
- name: Restore cached keys
uses: actions/cache/restore@v3
with:
path: keys
key: keys-folder

- name: Install LXD-UI dependencies
run: |
set -x
sudo chmod 0777 ../lxd-ui
dotrun install
- name: Run LXD-UI
env:
ENVIRONMENT: devel
PORT: 8407
LXD_UI_BACKEND_IP: 172.17.0.1
run: |
dotrun &
curl --head --fail --retry-delay 2 --retry 100 --retry-connrefused --insecure https://localhost:8407
- name: Set keys permissions
run: |
set -x
sudo chmod -R 0666 keys
sudo chmod 0777 keys
- name: Save keys
uses: actions/cache/save@v3
with:
path: keys
key: keys-folder

- name: Install LXD
uses: canonical/[email protected]
with:
channel: "latest/edge"

- name: Setup LXD
shell: bash
run: |
set -x
sudo lxc config set core.https_address "[::]:8443"
sudo lxc config trust add keys/lxd-ui.crt
sudo lxc config set cluster.https_address "127.0.0.1"
sudo lxc cluster enable local
- uses: actions/setup-node@v4
with:
node-version: 18

- name: Install Playwright Browser
run: npx playwright install --with-deps chromium

- name: Run tests with coverage
shell: bash
run: |
set -x
sudo chmod -R 0777 ../lxd-ui
yarn test-coverage
- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: lxd-coverage
path: coverage/playwright-report
retention-days: 1

publish-coverage-report:
name: publish-coverage-report
runs-on: ubuntu-latest
needs: e2e-coverage
continue-on-error: true
steps:
- uses: actions/checkout@v4
with:
ref: gh-pages
token: ${{ secrets.GITHUB_TOKEN }}
- name: Cleanup coverage directory
run: |
rm -rf coverage
mkdir coverage
- name: Download coverage report artifact
uses: actions/download-artifact@v4
with:
name: lxd-coverage
path: coverage
# user git configs are needed for git commands to work
# actual authentication is done using secrets.GITHUB_TOKEN with write permission
- name: Set Git User
run: |
git config --global user.email "[email protected]"
git config --global user.name "GitHub Action"
- name: Push coverage Report
timeout-minutes: 3
run: |
git add .
git commit -m "workflow: update coverage report"
# In case of another action job pushing to gh-pages while we are rebasing for the current job
while true; do
git pull --rebase
if [ $? -ne 0 ]; then
echo "Failed to rebase. Please review manually."
exit 1
fi
git push
if [ $? -eq 0 ]; then
echo "Successfully pushed HTML report to repo."
exit 0
fi
done
- name: Output Report URL as Worfklow Annotation
run: |
FULL_HTML_REPORT_URL=https://canonical.github.io/lxd-ui/coverage
echo "::notice title=Published Playwright Test Report::$FULL_HTML_REPORT_URL"
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,6 @@ dist/
/test-results/
/playwright-report/
/playwright/.cache/
/coverage

haproxy-local.cfg
haproxy-local.cfg
15 changes: 13 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"license": "LGPL-3.0-only",
"type": "module",
"scripts": {
"clean": "rm -rf node_modules yarn-error.log *.log build/ .jekyll-metadata .bundle playwright-report test-results haproxy-local.cfg",
"clean": "rm -rf node_modules yarn-error.log *.log build/ .jekyll-metadata .bundle playwright-report test-results haproxy-local.cfg coverage",
"build-html": "cp build/ui/index.html build/index.html",
"build": "npx vite build && yarn build-html",
"format-js-eslint": "eslint 'src/**/*.{json,jsx,tsx,ts}' 'tests/**/*.ts' --fix",
Expand All @@ -21,7 +21,11 @@
"serve": "./entrypoint",
"test-js": "vitest --run",
"test-e2e-edge": "npx playwright test --project chromium:lxd-latest-edge firefox:lxd-latest-edge",
"test-e2e-stable": "npx playwright test --project chromium:lxd-5.0-stable firefox:lxd-5.0-stable"
"test-e2e-stable": "npx playwright test --project chromium:lxd-5.0-stable firefox:lxd-5.0-stable",
"test-coverage": "yarn test-js-coverage ; yarn test-e2e-coverage ; yarn test-report-coverage",
"test-js-coverage": "vitest --run --coverage",
"test-e2e-coverage": "rm -rf coverage/playwright* ; PW_TEST_HTML_REPORT_OPEN='never' npx playwright test --project coverage",
"test-report-coverage": "cp coverage/unit/coverage-final.json coverage/playwright/ ; nyc report --reporter html --reporter cobertura --reporter text-summary --temp-dir coverage/playwright --report-dir coverage/playwright-report --exclude 'src/lib/**' --exclude 'src/types/**'"
},
"dependencies": {
"@canonical/react-components": "0.50.2",
Expand Down Expand Up @@ -54,6 +58,7 @@
"@babel/preset-react": "7.23.3",
"@babel/preset-typescript": "7.23.3",
"@playwright/test": "1.41.0",
"@types/convert-source-map": "2.0.3",
"@types/cytoscape-popper": "2.0.4",
"@types/node-forge": "1.3.11",
"@types/react": "18.2.48",
Expand All @@ -64,15 +69,19 @@
"@typescript-eslint/eslint-plugin": "6.19.0",
"@typescript-eslint/parser": "6.19.0",
"@vitejs/plugin-react": "4.2.1",
"@vitest/coverage-istanbul": "1.2.2",
"autoprefixer": "10.4.16",
"babel-plugin-istanbul": "6.1.1",
"concurrently": "8.2.2",
"convert-source-map": "2.0.0",
"eslint": "8.56.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-prettier": "5.1.3",
"eslint-plugin-react": "7.33.2",
"husky": "8.0.3",
"lint-staged": "15.2.0",
"monaco-editor": "0.45.0",
"nyc": "^15.1.0",
"postcss": "8.4.33",
"postcss-cli": "11.0.0",
"prettier": "3.2.4",
Expand All @@ -83,7 +92,9 @@
"stylelint-prettier": "5.0.0",
"stylelint-scss": "6.0.0",
"typescript": "5.3.3",
"v8-to-istanbul": "9.2.0",
"vite": "4.5.2",
"vite-plugin-istanbul": "5.0.0",
"vite-tsconfig-paths": "4.3.1",
"vitest": "1.2.1"
},
Expand Down
12 changes: 10 additions & 2 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import { TestOptions } from "./tests/fixtures/lxd-test";
const config: PlaywrightTestConfig<TestOptions> = {
testDir: "./tests",
/* Maximum time one test can run for. */
timeout: 120 * 1000,
timeout: 120_000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 120 * 1000,
timeout: 120_000,
},
/* Run tests in files in parallel */
fullyParallel: true,
Expand Down Expand Up @@ -70,6 +70,14 @@ const config: PlaywrightTestConfig<TestOptions> = {
lxdVersion: "latest-edge",
},
},
{
name: "coverage",
use: {
...devices["Desktop Chrome"],
lxdVersion: "latest-edge",
hasCoverage: true,
},
},
],
};

Expand Down
5 changes: 5 additions & 0 deletions src/util/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { LxdProfile } from "types/profile";
import { LxdNetwork } from "types/network";
import { LxdStorageVolume } from "types/storage";
import { Dispatch, SetStateAction } from "react";
import crypto from "crypto";

export const UNDEFINED_DATE = "0001-01-01T00:00:00Z";

Expand Down Expand Up @@ -260,3 +261,7 @@ export const getAbsoluteHeightBelow = (belowId: string): number => {
parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
return element.offsetHeight + margin + padding + 1;
};

export const generateUUID = (): string => {
return crypto.randomBytes(16).toString("hex");
};
71 changes: 71 additions & 0 deletions tests/fixtures/coverage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Page } from "@playwright/test";
import fs from "fs";
import { fromSource, removeMapFileComments } from "convert-source-map";
import v8ToIstanbul from "v8-to-istanbul";
import { CoverageMapData } from "istanbul-lib-coverage";
import { generateUUID } from "util/helpers";

export const startCoverage = async (
page: Page,
hasCoverage: boolean,
): Promise<void> => {
if (!hasCoverage) {
return;
}
await page.coverage.startJSCoverage();
};

export const finishCoverage = async (
page: Page,
hasCoverage: boolean,
): Promise<void> => {
if (!hasCoverage) {
return;
}
const coverage = await page.coverage.stopJSCoverage();
for (const entry of coverage) {
if (entry.url.endsWith(".css")) {
continue;
}
if (entry.url.includes("@vite")) {
continue;
}
if (entry.url.includes("spice")) {
continue;
}
const fileMatcher = entry.url.match(/http(s)*:\/\/.*:8407\/(?<file>.*)/);
if (!fileMatcher?.groups) {
continue;
}
const path = fileMatcher.groups.file;
const source = removeMapFileComments(entry.source ?? "");
const sourceMap = fromSource(entry.source ?? "") as { sourcemap: string };

const converter = v8ToIstanbul(path, 0, {
source,
sourceMap,
});
await converter.load();
converter.applyCoverage(entry.functions);
const istanbulCoverage = converter.toIstanbul() as CoverageMapData & {
[key: string]: { _coverageSchema: string };
};

// a unique name for this report
const uuid = generateUUID();

// _coverageSchema is mandatory for nyc to parse the report
Object.entries(istanbulCoverage).forEach(([key]) => {
istanbulCoverage[key]["_coverageSchema"] = uuid;
});

const outDir = "coverage/playwright";
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir, { recursive: true });
}
fs.writeFileSync(
`${outDir}/playwright_coverage_${uuid}.json`,
JSON.stringify(istanbulCoverage),
);
}
};
13 changes: 13 additions & 0 deletions tests/fixtures/lxd-test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { test as base } from "@playwright/test";
import { finishCoverage, startCoverage } from "./coverage";

export type LxdVersions = "5.0-stable" | "latest-edge";
export type TestOptions = {
lxdVersion: LxdVersions;
hasCoverage: boolean;
};

export const test = base.extend<TestOptions>({
lxdVersion: ["latest-edge", { option: true }],
hasCoverage: [false, { option: true }],
});

test.beforeEach(async ({ page, hasCoverage }) => {
await startCoverage(page, hasCoverage);
});

test.afterEach(async ({ page, hasCoverage }) => {
await finishCoverage(page, hasCoverage);
});

export const expect = test.expect;
1 change: 1 addition & 0 deletions tests/helpers/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const deleteNetwork = async (page: Page, network: string) => {
};

export const visitNetwork = async (page: Page, network: string) => {
await page.goto("/ui/");
await page.getByTitle("Networks (default)").click();
await page.getByRole("link", { name: network }).first().click();
};
Expand Down
Loading

0 comments on commit 0427e00

Please sign in to comment.