CI: flaky test monitor (#7007)

This commit is contained in:
Sid
2026-02-02 17:16:27 +05:30
committed by GitHub
parent 0f0c2b5912
commit 06dd5c14d5
7 changed files with 343 additions and 36 deletions

View File

@@ -0,0 +1,70 @@
const fs = require('fs');
const { execSync } = require('child_process');
// Check if flaky-tests.json exists
if (!fs.existsSync('flaky-tests.json')) {
console.log('No flaky-tests.json found');
process.exit(0);
}
// Get changed files in PR
let changedFiles = [];
try {
changedFiles = execSync('git diff --name-only origin/main...HEAD')
.toString()
.split('\n')
.filter(f => f.endsWith('.spec.ts'));
} catch (error) {
console.log('Could not determine changed files:', error.message);
process.exit(0);
}
if (changedFiles.length === 0) {
console.log('No test files were modified in this PR');
process.exit(0);
}
// Read flaky tests
const flakyTests = JSON.parse(fs.readFileSync('flaky-tests.json', 'utf8'));
if (flakyTests.length === 0) {
console.log('No flaky/failed tests found');
process.exit(0);
}
// Find modified flaky tests
const modifiedFlakyTests = flakyTests.filter(test =>
changedFiles.some(file => test.file.includes(file))
);
if (modifiedFlakyTests.length === 0) {
console.log('No modified test files are flaky');
process.exit(0);
}
// Generate comment markdown
let comment = '## ⚠️ Warning: You modified flaky/failed test files\n\n';
comment += 'The following test files you modified have reliability issues:\n\n';
modifiedFlakyTests.forEach(test => {
const testType = test.status === 'failed' ? '❌ Failed' : '⚠️ Flaky';
comment += `### ${testType}: \`${test.file}\`\n`;
comment += `**Test:** ${test.testTitle}\n`;
comment += `**Status:** ${test.status}\n`;
if (test.retryAttempt > 0) {
comment += `**Retry Attempt:** ${test.retryAttempt}\n`;
}
comment += '\n**To debug locally, run:**\n';
comment += '```bash\n';
comment += `npx playwright test ${test.file} --repeat-each=5 --workers=1\n`;
comment += '```\n\n';
});
comment += '---\n';
comment += '**Note:** Flaky tests passed after retrying, failed tests did not pass. ';
comment += 'Please investigate and fix the root cause before merging.\n';
// Save comment to file for GitHub Action to post
fs.writeFileSync('pr-comment.md', comment);
console.log(`Found ${modifiedFlakyTests.length} modified flaky tests`);

78
.github/scripts/detect-flaky-tests.js vendored Normal file
View File

@@ -0,0 +1,78 @@
const fs = require('fs');
// Read Playwright JSON report
const resultsPath = 'playwright-report/results.json';
if (!fs.existsSync(resultsPath)) {
console.log('No Playwright results found at', resultsPath);
process.exit(0);
}
const results = JSON.parse(fs.readFileSync(resultsPath, 'utf8'));
// Extract flaky tests
// A test is flaky if: status === "passed" AND retry > 0
// A test is failed if: status === "failed"
// This means it failed initially but passed on retry OR failed completely
const flakyTests = [];
function traverseSuites(suites) {
for (const suite of suites) {
// Process specs in this suite
for (const spec of suite.specs || []) {
for (const test of spec.tests || []) {
// Check each test result
for (const result of test.results || []) {
// Track two types of problematic tests:
// 1. Flaky: passed on a retry attempt (retry > 0)
// 2. Failed: failed on all attempts
if ((result.status === 'passed' && result.retry > 0) || result.status === 'failed') {
flakyTests.push({
file: spec.file,
title: spec.title,
testTitle: spec.title,
line: spec.line,
status: result.status,
retryAttempt: result.retry
});
break; // Only record once per test
}
}
}
}
// Recursively process nested suites
if (suite.suites && suite.suites.length > 0) {
traverseSuites(suite.suites);
}
}
}
traverseSuites(results.suites || []);
// Save flaky tests to JSON
fs.writeFileSync('flaky-tests.json', JSON.stringify(flakyTests, null, 2));
// Generate markdown report
let markdown = '## ⚠️ Flaky/Failed Tests Detected\n\n';
markdown += 'The following tests are problematic:\n\n';
flakyTests.forEach(test => {
const testType = test.status === 'failed' ? '❌ Failed' : '⚠️ Flaky';
markdown += `### ${testType}: \`${test.file}\`\n`;
markdown += `- **Test:** ${test.testTitle}\n`;
markdown += `- **Status:** ${test.status}\n`;
if (test.retryAttempt > 0) {
markdown += `- **Retry Attempt:** ${test.retryAttempt}\n`;
}
markdown += `- **Debug command:**\n`;
markdown += '```bash\n';
markdown += `npx playwright test ${test.file} --repeat-each=5 --workers=1\n`;
markdown += '```\n\n';
});
fs.writeFileSync('flaky-report.md', markdown);
console.log(`Found ${flakyTests.length} flaky/failed tests`);
process.exit(flakyTests.length > 0 ? 1 : 0);

View File

@@ -0,0 +1,119 @@
name: Flaky Test Detector
on:
pull_request:
branches: [main]
paths:
- 'tests/**/*.spec.ts'
permissions:
contents: read
pull-requests: write
checks: write
jobs:
detect-flaky-tests:
name: Detect Flaky Tests
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0 # Need full history to compare with main
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get --no-install-recommends install -y \
libglib2.0-0 libnss3 libdbus-1-3 libatk1.0-0 libatk-bridge2.0-0 \
libcups2 libgtk-3-0 libasound2t64 xvfb
- name: Install npm dependencies
run: |
npm ci --legacy-peer-deps
sudo chown root /home/runner/work/bruno/bruno/node_modules/electron/dist/chrome-sandbox
sudo chmod 4755 /home/runner/work/bruno/bruno/node_modules/electron/dist/chrome-sandbox
- name: Install test collection dependencies
run: npm ci --prefix packages/bruno-tests/collection
- name: Build libraries
run: |
npm run build:graphql-docs
npm run build:bruno-query
npm run build:bruno-common
npm run sandbox:bundle-libraries --workspace=packages/bruno-js
npm run build:bruno-converters
npm run build:bruno-requests
npm run build:schema-types
npm run build:bruno-filestore
- name: Run Playwright tests
run: xvfb-run npm run test:e2e
continue-on-error: true # Continue even if tests fail
- name: Detect flaky tests
id: detect
run: node .github/scripts/detect-flaky-tests.js
continue-on-error: true # Don't fail workflow if flaky tests found
- name: Check modified flaky tests
id: check-modified
run: node .github/scripts/comment-on-flaky-tests.js
continue-on-error: true
- name: Post PR comment
if: hashFiles('pr-comment.md') != ''
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const comment = fs.readFileSync('pr-comment.md', 'utf8');
// Check if we already commented
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number
});
const botComment = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('Warning: You modified flaky/failed test files')
);
if (botComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: comment
});
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}
- name: Upload flaky test artifacts
if: always()
uses: actions/upload-artifact@v6
with:
name: flaky-test-results
path: |
flaky-tests.json
flaky-report.md
playwright-report/
retention-days: 30

View File

@@ -1,6 +1,6 @@
import { defineConfig } from '@playwright/test';
const reporter: any[] = [['list'], ['html']];
const reporter: any[] = [['list'], ['html'], ['json', { outputFile: 'playwright-report/results.json' }]];
if (process.env.CI) {
reporter.push(['github']);

View File

@@ -73,6 +73,9 @@ test.describe('Import Insomnia v4 Collection - Environment Import', () => {
.first()
.click();
// Wait for environment variables to load - use input selector as it's more reliable
await expect(page.locator('input[value="baseUrl"]')).toBeVisible({ timeout: 10000 });
// **Assertion 1: Basic Variables (Top-level keys)**
// Verifies that simple key-value pairs from the base environment are imported correctly
const v4BaseUrlInput = page.locator('input[value="baseUrl"]');
@@ -81,8 +84,10 @@ test.describe('Import Insomnia v4 Collection - Environment Import', () => {
await expect(v4AuthTokenInput).toBeVisible();
// Assert: Top-level string values are preserved exactly as in the source
await expect(page.getByTestId('env-var-row-baseUrl').locator('.CodeMirror-line').first()).toHaveText('https://api.example.com');
await expect(page.getByTestId('env-var-row-authToken').locator('.CodeMirror-line').first()).toHaveText('your_auth_token_here');
const baseUrlRow = page.locator('tbody tr').filter({ has: page.locator('input[value="baseUrl"]') });
await expect(baseUrlRow.locator('.CodeMirror-line').first()).toHaveText('https://api.example.com');
const authTokenRow = page.locator('tbody tr').filter({ has: page.locator('input[value="authToken"]') });
await expect(authTokenRow.locator('.CodeMirror-line').first()).toHaveText('your_auth_token_here');
// **Assertion 2: Nested Object Flattening**
// Verifies that nested objects are flattened to dot-notation keys (e.g., user.name, user.id)
@@ -92,9 +97,11 @@ test.describe('Import Insomnia v4 Collection - Environment Import', () => {
await expect(v4UserIdInput).toBeVisible();
// Assert: Nested object properties are accessible via dot notation
await expect(page.getByTestId('env-var-row-user.name').locator('.CodeMirror-line').first()).toHaveText('admin');
const userNameRow = page.locator('tbody tr').filter({ has: page.locator('input[value="user.name"]') });
await expect(userNameRow.locator('.CodeMirror-line').first()).toHaveText('admin');
// Assert: Numeric values are converted to strings and preserved
await expect(page.getByTestId('env-var-row-user.id').locator('.CodeMirror-line').first()).toHaveText('123');
const userIdRow = page.locator('tbody tr').filter({ has: page.locator('input[value="user.id"]') });
await expect(userIdRow.locator('.CodeMirror-line').first()).toHaveText('123');
// **Assertion 3: Array Flattening**
// Verifies that arrays are flattened using JavaScript-style square bracket notation (e.g., user.roles[0], user.roles[1])
@@ -104,8 +111,10 @@ test.describe('Import Insomnia v4 Collection - Environment Import', () => {
await expect(v4UserRoles1Input).toBeVisible();
// Assert: Array elements are accessible via JavaScript-style square bracket notation
await expect(page.getByTestId('env-var-row-user.roles[0]').locator('.CodeMirror-line').first()).toHaveText('admin');
await expect(page.getByTestId('env-var-row-user.roles[1]').locator('.CodeMirror-line').first()).toHaveText('user');
const userRoles0Row = page.locator('tbody tr').filter({ has: page.locator('input[value="user.roles[0]"]') });
await expect(userRoles0Row.locator('.CodeMirror-line').first()).toHaveText('admin');
const userRoles1Row = page.locator('tbody tr').filter({ has: page.locator('input[value="user.roles[1]"]') });
await expect(userRoles1Row.locator('.CodeMirror-line').first()).toHaveText('user');
});
await test.step('Test Staging Environment - verify merging with base', async () => {
@@ -120,14 +129,16 @@ test.describe('Import Insomnia v4 Collection - Environment Import', () => {
const v4StagingBaseUrlInput = page.locator('input[value="baseUrl"]');
await expect(v4StagingBaseUrlInput).toBeVisible();
// Assert: Staging overrides baseUrl with its own value
await expect(page.getByTestId('env-var-row-baseUrl').locator('.CodeMirror-line').first()).toHaveText('https://staging-api.example.com');
const stagingBaseUrlRow = page.locator('tbody tr').filter({ has: page.locator('input[value="baseUrl"]') });
await expect(stagingBaseUrlRow.locator('.CodeMirror-line').first()).toHaveText('https://staging-api.example.com');
// **Assertion 2: Top-level Variable Inheritance**
// Verifies that staging environment inherits base environment values when not overridden
const v4StagingAuthTokenInput = page.locator('input[value="authToken"]');
await expect(v4StagingAuthTokenInput).toBeVisible();
// Assert: Staging inherits authToken from base (not overridden in staging)
await expect(page.getByTestId('env-var-row-authToken').locator('.CodeMirror-line').first()).toHaveText('your_auth_token_here');
const stagingAuthTokenRow = page.locator('tbody tr').filter({ has: page.locator('input[value="authToken"]') });
await expect(stagingAuthTokenRow.locator('.CodeMirror-line').first()).toHaveText('your_auth_token_here');
// **Assertion 3: Nested Object Variable Override and Inheritance**
// Verifies that nested object properties can be selectively overridden while inheriting others
@@ -139,11 +150,14 @@ test.describe('Import Insomnia v4 Collection - Environment Import', () => {
await expect(v4StagingUserRoles0Input).toBeVisible();
// Assert: Staging overrides user.name with its own value
await expect(page.getByTestId('env-var-row-user.name').locator('.CodeMirror-line').first()).toHaveText('staging_admin');
const stagingUserNameRow = page.locator('tbody tr').filter({ has: page.locator('input[value="user.name"]') });
await expect(stagingUserNameRow.locator('.CodeMirror-line').first()).toHaveText('staging_admin');
// Assert: Staging inherits user.id from base (not overridden in staging)
await expect(page.getByTestId('env-var-row-user.id').locator('.CodeMirror-line').first()).toHaveText('123');
const stagingUserIdRow = page.locator('tbody tr').filter({ has: page.locator('input[value="user.id"]') });
await expect(stagingUserIdRow.locator('.CodeMirror-line').first()).toHaveText('123');
// Assert: Staging inherits user.roles[0] from base (not overridden in staging)
await expect(page.getByTestId('env-var-row-user.roles[0]').locator('.CodeMirror-line').first()).toHaveText('admin');
const stagingUserRoles0Row = page.locator('tbody tr').filter({ has: page.locator('input[value="user.roles[0]"]') });
await expect(stagingUserRoles0Row.locator('.CodeMirror-line').first()).toHaveText('admin');
});
await test.step('Test Development Environment - verify new variables', async () => {
@@ -161,9 +175,11 @@ test.describe('Import Insomnia v4 Collection - Environment Import', () => {
await expect(v4DevAuthTokenInput).toBeVisible();
// Assert: Development overrides baseUrl with its own value
await expect(page.getByTestId('env-var-row-baseUrl').locator('.CodeMirror-line').first()).toHaveText('https://dev-api.example.com');
const devBaseUrlRow = page.locator('tbody tr').filter({ has: page.locator('input[value="baseUrl"]') });
await expect(devBaseUrlRow.locator('.CodeMirror-line').first()).toHaveText('https://dev-api.example.com');
// Assert: Development overrides authToken with its own value
await expect(page.getByTestId('env-var-row-authToken').locator('.CodeMirror-line').first()).toHaveText('dev_token_123');
const devAuthTokenRow = page.locator('tbody tr').filter({ has: page.locator('input[value="authToken"]') });
await expect(devAuthTokenRow.locator('.CodeMirror-line').first()).toHaveText('dev_token_123');
// **Assertion 2: New Nested Variables Addition**
// Verifies that development environment can add completely new nested variables not present in base
@@ -173,9 +189,11 @@ test.describe('Import Insomnia v4 Collection - Environment Import', () => {
await expect(v4NewFeatureVersionInput).toBeVisible();
// Assert: New boolean variable is added and converted to string
await expect(page.getByTestId('env-var-row-newFeature.enabled').locator('.CodeMirror-line').first()).toHaveText('true');
const newFeatureEnabledRow = page.locator('tbody tr').filter({ has: page.locator('input[value="newFeature.enabled"]') });
await expect(newFeatureEnabledRow.locator('.CodeMirror-line').first()).toHaveText('true');
// Assert: New numeric variable is added and converted to string with full precision
await expect(page.getByTestId('env-var-row-newFeature.version').locator('.CodeMirror-line').first()).toHaveText('2.099123123');
const newFeatureVersionRow = page.locator('tbody tr').filter({ has: page.locator('input[value="newFeature.version"]') });
await expect(newFeatureVersionRow.locator('.CodeMirror-line').first()).toHaveText('2.099123123');
});
await test.step('Close environment tab', async () => {

View File

@@ -78,8 +78,10 @@ test.describe('Import Insomnia v5 Collection - Environment Import', () => {
await expect(authTokenInput).toBeVisible();
// Assert: Top-level string values are preserved exactly as in the source
await expect(page.getByTestId('env-var-row-base_url').locator('.CodeMirror-line').first()).toHaveText('https://api.example.com');
await expect(page.getByTestId('env-var-row-auth_token').locator('.CodeMirror-line').first()).toHaveText('your_auth_token_here');
const baseUrlRow = page.locator('tbody tr').filter({ has: page.locator('input[value=\"base_url\"]') });
await expect(baseUrlRow.locator('.CodeMirror-line').first()).toHaveText('https://api.example.com');
const authTokenRow = page.locator('tbody tr').filter({ has: page.locator('input[value=\"auth_token\"]') });
await expect(authTokenRow.locator('.CodeMirror-line').first()).toHaveText('your_auth_token_here');
// **Assertion 2: Nested Object Flattening**
// Verifies that nested objects are flattened to dot-notation keys (e.g., user.name, user.id)
@@ -89,9 +91,11 @@ test.describe('Import Insomnia v5 Collection - Environment Import', () => {
await expect(userIdInput).toBeVisible();
// Assert: Nested object properties are accessible via dot notation
await expect(page.getByTestId('env-var-row-user.name').locator('.CodeMirror-line').first()).toHaveText('admin');
const userNameRow = page.locator('tbody tr').filter({ has: page.locator('input[value=\"user.name\"]') });
await expect(userNameRow.locator('.CodeMirror-line').first()).toHaveText('admin');
// Assert: Numeric values are converted to strings and preserved
await expect(page.getByTestId('env-var-row-user.id').locator('.CodeMirror-line').first()).toHaveText('123');
const userIdRow = page.locator('tbody tr').filter({ has: page.locator('input[value=\"user.id\"]') });
await expect(userIdRow.locator('.CodeMirror-line').first()).toHaveText('123');
// **Assertion 3: Array Flattening**
// Verifies that arrays are flattened using JavaScript-style square bracket notation (e.g., user.roles[0], user.roles[1])
@@ -101,8 +105,10 @@ test.describe('Import Insomnia v5 Collection - Environment Import', () => {
await expect(userRoles1Input).toBeVisible();
// Assert: Array elements are accessible via JavaScript-style square bracket notation
await expect(page.getByTestId('env-var-row-user.roles[0]').locator('.CodeMirror-line').first()).toHaveText('admin');
await expect(page.getByTestId('env-var-row-user.roles[1]').locator('.CodeMirror-line').first()).toHaveText('user');
const userRoles0Row = page.locator('tbody tr').filter({ has: page.locator('input[value=\"user.roles[0]\"]') });
await expect(userRoles0Row.locator('.CodeMirror-line').first()).toHaveText('admin');
const userRoles1Row = page.locator('tbody tr').filter({ has: page.locator('input[value=\"user.roles[1]\"]') });
await expect(userRoles1Row.locator('.CodeMirror-line').first()).toHaveText('user');
// **Assertion 4: Deeply Nested Config Objects**
// Verifies that deeply nested objects are properly flattened (e.g., config.timeout, config.debug)
@@ -112,9 +118,11 @@ test.describe('Import Insomnia v5 Collection - Environment Import', () => {
await expect(configDebugInput).toBeVisible();
// Assert: Numeric values in nested objects are converted to strings
await expect(page.getByTestId('env-var-row-config.timeout').locator('.CodeMirror-line').first()).toHaveText('30000');
const configTimeoutRow = page.locator('tbody tr').filter({ has: page.locator('input[value=\"config.timeout\"]') });
await expect(configTimeoutRow.locator('.CodeMirror-line').first()).toHaveText('30000');
// Assert: Boolean values in nested objects are converted to strings
await expect(page.getByTestId('env-var-row-config.debug').locator('.CodeMirror-line').first()).toHaveText('true');
const configDebugRow = page.locator('tbody tr').filter({ has: page.locator('input[value=\"config.debug\"]') });
await expect(configDebugRow.locator('.CodeMirror-line').first()).toHaveText('true');
});
await test.step('Test Staging Environment - verify merging and overrides', async () => {
@@ -129,14 +137,16 @@ test.describe('Import Insomnia v5 Collection - Environment Import', () => {
const stagingBaseUrlInput = page.locator('input[value="base_url"]');
await expect(stagingBaseUrlInput).toBeVisible();
// Assert: Staging overrides base_url with its own value
await expect(page.getByTestId('env-var-row-base_url').locator('.CodeMirror-line').first()).toHaveText('https://staging-api.example.com');
const baseUrlRow = page.locator('tbody tr').filter({ has: page.locator('input[value=\"base_url\"]') });
await expect(baseUrlRow.locator('.CodeMirror-line').first()).toHaveText('https://staging-api.example.com');
// **Assertion 2: Top-level Variable Inheritance**
// Verifies that staging environment inherits base environment values when not overridden
const stagingAuthTokenInput = page.locator('input[value="auth_token"]');
await expect(stagingAuthTokenInput).toBeVisible();
// Assert: Staging inherits auth_token from base (not overridden in staging)
await expect(page.getByTestId('env-var-row-auth_token').locator('.CodeMirror-line').first()).toHaveText('your_auth_token_here');
const authTokenRow = page.locator('tbody tr').filter({ has: page.locator('input[value=\"auth_token\"]') });
await expect(authTokenRow.locator('.CodeMirror-line').first()).toHaveText('your_auth_token_here');
// **Assertion 3: Nested Object Variable Override and Inheritance**
// Verifies that nested object properties can be selectively overridden while inheriting others
@@ -146,9 +156,11 @@ test.describe('Import Insomnia v5 Collection - Environment Import', () => {
await expect(stagingUserIdInput).toBeVisible();
// Assert: Staging overrides user.name with its own value
await expect(page.getByTestId('env-var-row-user.name').locator('.CodeMirror-line').first()).toHaveText('staging_admin');
const userNameRow = page.locator('tbody tr').filter({ has: page.locator('input[value=\"user.name\"]') });
await expect(userNameRow.locator('.CodeMirror-line').first()).toHaveText('staging_admin');
// Assert: Staging inherits user.id from base (not overridden in staging)
await expect(page.getByTestId('env-var-row-user.id').locator('.CodeMirror-line').first()).toHaveText('123');
const userIdRow = page.locator('tbody tr').filter({ has: page.locator('input[value=\"user.id\"]') });
await expect(userIdRow.locator('.CodeMirror-line').first()).toHaveText('123');
// **Assertion 4: Deeply Nested Config Override**
// Verifies that deeply nested object properties can be overridden
@@ -158,9 +170,11 @@ test.describe('Import Insomnia v5 Collection - Environment Import', () => {
await expect(stagingConfigDebugInput).toBeVisible();
// Assert: Staging overrides config.timeout with its own value
await expect(page.getByTestId('env-var-row-config.timeout').locator('.CodeMirror-line').first()).toHaveText('60000');
const configTimeoutRow = page.locator('tbody tr').filter({ has: page.locator('input[value=\"config.timeout\"]') });
await expect(configTimeoutRow.locator('.CodeMirror-line').first()).toHaveText('60000');
// Assert: Staging overrides config.debug with its own value
await expect(page.getByTestId('env-var-row-config.debug').locator('.CodeMirror-line').first()).toHaveText('false');
const configDebugRow = page.locator('tbody tr').filter({ has: page.locator('input[value=\"config.debug\"]') });
await expect(configDebugRow.locator('.CodeMirror-line').first()).toHaveText('false');
});
await test.step('Test Development Environment - verify new variables', async () => {
@@ -178,9 +192,11 @@ test.describe('Import Insomnia v5 Collection - Environment Import', () => {
await expect(devAuthTokenInput).toBeVisible();
// Assert: Development overrides base_url with its own value
await expect(page.getByTestId('env-var-row-base_url').locator('.CodeMirror-line').first()).toHaveText('https://dev-api.example.com');
const baseUrlRow = page.locator('tbody tr').filter({ has: page.locator('input[value=\"base_url\"]') });
await expect(baseUrlRow.locator('.CodeMirror-line').first()).toHaveText('https://dev-api.example.com');
// Assert: Development overrides auth_token with its own value
await expect(page.getByTestId('env-var-row-auth_token').locator('.CodeMirror-line').first()).toHaveText('dev_token_123');
const authTokenRow = page.locator('tbody tr').filter({ has: page.locator('input[value=\"auth_token\"]') });
await expect(authTokenRow.locator('.CodeMirror-line').first()).toHaveText('dev_token_123');
// **Assertion 2: New Nested Variables Addition**
// Verifies that development environment can add completely new nested variables not present in base
@@ -190,16 +206,19 @@ test.describe('Import Insomnia v5 Collection - Environment Import', () => {
await expect(newFeatureVersionInput).toBeVisible();
// Assert: New boolean variable is added and converted to string
await expect(page.getByTestId('env-var-row-new_feature.enabled').locator('.CodeMirror-line').first()).toHaveText('true');
const newFeatureEnabledRow = page.locator('tbody tr').filter({ has: page.locator('input[value=\"new_feature.enabled\"]') });
await expect(newFeatureEnabledRow.locator('.CodeMirror-line').first()).toHaveText('true');
// Assert: New numeric variable is added and converted to string with full precision
await expect(page.getByTestId('env-var-row-new_feature.version').locator('.CodeMirror-line').first()).toHaveText('2.099123123');
const newFeatureVersionRow = page.locator('tbody tr').filter({ has: page.locator('input[value=\"new_feature.version\"]') });
await expect(newFeatureVersionRow.locator('.CodeMirror-line').first()).toHaveText('2.099123123');
// **Assertion 3: Base Variable Inheritance**
// Verifies that development environment still inherits base variables that are not overridden
const devUserRoles0Input = page.locator('input[value="user.roles[0]"]');
await expect(devUserRoles0Input).toBeVisible();
// Assert: Development inherits user.roles[0] from base (not overridden in development)
await expect(page.getByTestId('env-var-row-user.roles[0]').locator('.CodeMirror-line').first()).toHaveText('admin');
const userRoles0Row = page.locator('tbody tr').filter({ has: page.locator('input[value=\"user.roles[0]\"]') });
await expect(userRoles0Row.locator('.CodeMirror-line').first()).toHaveText('admin');
});
await test.step('Close environment tab', async () => {

View File

@@ -702,7 +702,10 @@ const switchResponseFormat = async (page: Page, format: string) => {
await test.step(`Switch response format to ${format}`, async () => {
const responseFormatTab = page.getByTestId('format-response-tab');
await responseFormatTab.click();
await page.getByTestId('format-response-tab-dropdown').getByText(format).click();
// Wait for dropdown to be visible before clicking the format option
const dropdown = page.getByTestId('format-response-tab-dropdown');
await dropdown.waitFor({ state: 'visible' });
await dropdown.getByText(format).click();
});
};