diff --git a/.github/workflows/playwright-actions.yml b/.github/workflows/playwright-actions.yml index 342823675..8c11a2f48 100644 --- a/.github/workflows/playwright-actions.yml +++ b/.github/workflows/playwright-actions.yml @@ -14,6 +14,8 @@ jobs: - codebuild-content-services-fe-repo-${{ github.run_id }}-${{ github.run_attempt }} - instance-size:large - buildspec-override:true + env: + COVERAGE: ${{ vars.COVERAGE }} steps: - name: Checkout code @@ -69,6 +71,7 @@ jobs: echo "BASE_URL=https://stage.foo.redhat.com:1337" echo "CI=true" echo "RBAC=true" + echo "COVERAGE=$COVERAGE" } >> .env echo ".env file created successfully." @@ -176,7 +179,14 @@ jobs: run: sudo npm run patch:hosts - name: Run front-end and back-end setup in parallel - run: 'parallel --line-buffer --tagstring "[{#}]:" ::: "yarn install --frozen-lockfile && yarn build&& yarn playwright install chromium --only-shell" "cd content-sources-backend && make compose-down compose-clean compose-up"' + run: | + if [ "$COVERAGE" = "true" ]; then + echo "📊 Building with Istanbul coverage instrumentation..." + BUILD_CMD="yarn build:coverage" + else + BUILD_CMD="yarn build" + fi + parallel --line-buffer --tagstring "[{#}]:" ::: "yarn install --frozen-lockfile && $BUILD_CMD && yarn playwright install chromium --only-shell" "cd content-sources-backend && make compose-down compose-clean compose-up" - name: Frontend run (static) run: yarn fec static --port 8003 & @@ -206,7 +216,11 @@ jobs: run: while [[ "$(curl http://localhost:8000/api/content-sources/v1.0/repositories/ -H "$( ./scripts/header.sh 9999 1111)" | jq '.data | all(.status == "Valid")')" == "false" ]]; do sleep 5; done; - name: Run front-end Playwright tests - run: CURRENTS_PROJECT_ID=mMVOFH CURRENTS_RECORD_KEY=$CURRENTS_RECORD_KEY CURRENTS_CI_BUILD_ID="${{ github.repository }}-${{ github.run_id }}-${{ github.run_attempt }}" yarn playwright test + run: yarn playwright test + env: + CURRENTS_PROJECT_ID: ${{ secrets.CURRENTS_PROJECT_ID }} + CURRENTS_RECORD_KEY: ${{ secrets.CURRENTS_RECORD_KEY }} + CURRENTS_CI_BUILD_ID: ${{ github.repository }}-${{ github.run_id }}-${{ github.run_attempt }} - name: Publish front-end Test Report uses: ctrf-io/github-test-reporter@v1 diff --git a/_playwright-tests/Integration/NoSubsUserTest.spec.ts b/_playwright-tests/Integration/NoSubsUserTest.spec.ts index f296d29a1..d086e5673 100644 --- a/_playwright-tests/Integration/NoSubsUserTest.spec.ts +++ b/_playwright-tests/Integration/NoSubsUserTest.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from '@playwright/test'; +import { test, expect } from 'test-utils'; import { navigateToTemplates } from '../UI/helpers/navHelpers'; import { closeGenericPopupsIfExist } from '../UI/helpers/helpers'; diff --git a/_playwright-tests/Integration/switchToPreview.spec.ts b/_playwright-tests/Integration/switchToPreview.spec.ts index 39f03e41f..ffbf52bdf 100644 --- a/_playwright-tests/Integration/switchToPreview.spec.ts +++ b/_playwright-tests/Integration/switchToPreview.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from '@playwright/test'; +import { test, expect } from 'test-utils'; import { navigateToRepositories } from '../UI/helpers/navHelpers'; import { ensureInPreview } from '../authHelpers'; diff --git a/build-tools b/build-tools index 33f43f373..7e1d0363c 160000 --- a/build-tools +++ b/build-tools @@ -1 +1 @@ -Subproject commit 33f43f3739ab32c253ecf89e1adb7684f1f03b19 +Subproject commit 7e1d0363c211be0ac7f35103fb32cff49b42305d diff --git a/fec.config.js b/fec.config.js index 1d9d5766e..92ad22361 100644 --- a/fec.config.js +++ b/fec.config.js @@ -6,6 +6,48 @@ const { sentryWebpackPlugin } = require('@sentry/webpack-plugin'); const sassPrefix = insights.appname.replace(/-(\w)/g, (_, match) => match.toUpperCase()); const srcDir = path.resolve(__dirname, './src'); +/** + * Custom webpack plugin to add Istanbul coverage instrumentation. + * Active when COVERAGE=true environment variable is set. + */ +class IstanbulCoveragePlugin { + apply(compiler) { + if (process.env.COVERAGE === 'true') { + const options = compiler.options; + options.module = options.module || {}; + options.module.rules = options.module.rules || []; + + // Guard against duplicate rules on incremental multi-run builds + const hasIstanbulLoader = options.module.rules.some( + (rule) => + rule.use?.loader === '@jsdevtools/coverage-istanbul-loader' || + (typeof rule.use === 'string' && rule.use.includes('istanbul')) + ); + + if (hasIstanbulLoader) { + return; // Already configured + } + + console.log('[coverage] Adding Istanbul instrumentation using fec.config.js plugin'); + + options.module.rules.push({ + test: /\.(ts|tsx|js|jsx)$/, + include: srcDir, + exclude: /node_modules|\.test\.|\.spec\./, + enforce: 'post', + use: { + loader: '@jsdevtools/coverage-istanbul-loader', + options: { + esModules: true, + coverageGlobalScope: 'window', + coverageGlobalScopeFunc: false, + }, + }, + }); + } + } +} + module.exports = { sassPrefix: `.${sassPrefix}`, appUrl: '/insights/content', @@ -14,6 +56,8 @@ module.exports = { useProxy: true, interceptChromeConfig: false, plugins: [ + // Istanbul coverage plugin (active when COVERAGE=true) + new IstanbulCoveragePlugin(), ...(process.env.ENABLE_SENTRY ? [ sentryWebpackPlugin({ diff --git a/package.json b/package.json index 6aa38e0a8..c4ec7158d 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "test-ui": "yarn install && yarn playwright install && yarn playwright test", "build": "fec build", "build:prod": "yarn build", + "build:coverage": "COVERAGE=true fec build", "deploy": "npm-run-all build lint test", "lint": "eslint '{src,_playwright-tests}/**/*.{js,jsx,ts,tsx}' --no-error-on-unmatched-pattern --ignore-pattern '_playwright-tests/test-utils/*'", "lint:fix": "eslint --fix '{src,_playwright-tests}/**/*.{js,jsx,ts,tsx}' --no-error-on-unmatched-pattern --ignore-pattern '_playwright-tests/test-utils/*'", diff --git a/playwright.config.ts b/playwright.config.ts index d4bf2a1ff..472f5cea0 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,3 +1,9 @@ +import { + CurrentsConfig, + CurrentsFixtures, + currentsReporter, + CurrentsWorkerFixtures, +} from '@currents/playwright'; import { defineConfig, devices } from '@playwright/test'; import { config } from 'dotenv'; import path from 'path'; @@ -6,10 +12,32 @@ config({ path: path.join(__dirname, './.env'), quiet: true }); // Coverage setup flag: when true, run globalSetup to prepare coverage const enableCoverageSetup = process.env.COVERAGE === 'true'; +// Currents.dev configuration is created when both env vars are present +const currentsRecordKey = process.env.CURRENTS_RECORD_KEY; +const currentsProjectId = process.env.CURRENTS_PROJECT_ID; +const currentsConfig: CurrentsConfig | undefined = + currentsRecordKey && currentsProjectId + ? { + recordKey: currentsRecordKey, + projectId: currentsProjectId, + coverage: { + projects: ['UI', 'integration'], + }, + } + : undefined; + +// Warn in CI when coverage is enabled but Currents is not configured +if (process.env.CI && enableCoverageSetup && !currentsConfig) { + console.warn( + 'Warning: COVERAGE=true but CURRENTS_RECORD_KEY and/or CURRENTS_PROJECT_ID not set. ' + + 'Coverage data will be collected locally but not uploaded to Currents.dev.', + ); +} + /** * See https://playwright.dev/docs/test-configuration. */ -export default defineConfig({ +export default defineConfig({ testDir: './_playwright-tests/', // Run coverage global setup before tests (when COVERAGE=true) globalSetup: enableCoverageSetup ? './_playwright-tests/global-setup.coverage.ts' : undefined, @@ -24,7 +52,7 @@ export default defineConfig({ 'playwright-ctrf-json-reporter', { outputDir: 'playwright-ctrf', outputFile: 'playwright-ctrf.json' }, ], - ['@currents/playwright'], + ...(currentsConfig ? [currentsReporter(currentsConfig)] : []), ] : 'list', globalTimeout: (process.env.INTEGRATION ? 35 : 20) * 60 * 1000, @@ -56,6 +84,8 @@ export default defineConfig({ trace: 'on', screenshot: 'on', video: 'on', + // Currents.dev configuration options for coverage collection (only in CI) + ...(process.env.CI && currentsConfig ? { currentsConfigOptions: currentsConfig } : {}), }, projects: [ { name: 'setup', testMatch: /.*\.setup\.ts/, use: { trace: 'off' } },