Skip to content

Commit aaca8d6

Browse files
committed
test: Addressing review feedback
1 parent 435bfbd commit aaca8d6

6 files changed

Lines changed: 171 additions & 223 deletions

File tree

playwright/RUNNING_TESTS.md

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Running Playwright Tests
22

3-
The Playwright tests are configured to run against the **Red Hat stage environment** (`https://console.stage.redhat.com`).
3+
The Playwright tests are configured by default to run against the **local development proxy** (`https://stage.foo.redhat.com:1337`).
44

5-
This matches the pattern used by other HCC frontend E2E tests (e.g., rbac-ui) and is suitable for both local development and CI/CD (Konflux).
5+
This requires running the local dev server (`npm start`) and proper `/etc/hosts` configuration. For remote testing without a local proxy, you can override the base URL to `https://console.stage.redhat.com`.
66

77
## Prerequisites
88

@@ -24,47 +24,46 @@ E2E_PASSWORD=your-redhat-password
2424

2525
## Running Tests
2626

27-
### Direct Against Stage (Recommended)
27+
### Against Local Dev Proxy (Default)
2828

29-
Tests run directly against `https://console.stage.redhat.com` by default:
29+
Tests run against `https://stage.foo.redhat.com:1337` by default (requires `npm start`):
3030

3131
```bash
3232
# Run all migrated tests
33-
npx playwright test integrations.spec.ts notifications.spec.ts
33+
npx playwright test integrations-ui.spec.ts notifications-ui.spec.ts
3434

3535
# Run only integration tests
36-
npx playwright test integrations.spec.ts
36+
npx playwright test integrations-ui.spec.ts
3737

3838
# Run only notification tests
39-
npx playwright test notifications.spec.ts
39+
npx playwright test notifications-ui.spec.ts
4040

4141
# Run in UI mode (interactive debugging)
4242
npx playwright test --ui
4343

4444
# Run specific test suite
45-
npx playwright test integrations.spec.ts -g "Webhook Integration Creation"
45+
npx playwright test integrations-ui.spec.ts -g "Webhook Integration"
4646

4747
# Run with headed browser (see what's happening)
4848
npx playwright test --headed
4949

5050
# Debug a specific test
51-
npx playwright test integrations.spec.ts --debug
51+
npx playwright test integrations-ui.spec.ts --debug
5252
```
5353

54-
## Running Against Local Dev Server (Optional)
54+
## Running Against Remote Stage (Optional)
5555

56-
If you want to test against a local development proxy instead:
56+
If you want to test against remote stage without a local proxy:
5757

5858
```bash
5959
# Set environment variable to override the default
60-
export PLAYWRIGHT_BASE_URL="https://stage.foo.redhat.com:1337"
60+
export PLAYWRIGHT_BASE_URL="https://console.stage.redhat.com"
6161

6262
# Run tests
6363
npx playwright test
6464
```
6565

66-
**Note:** This requires `/etc/hosts` configuration and a running local dev server (`npm start`).
67-
Most developers and CI/CD should use the default stage environment instead.
66+
**Note:** Most local development uses the default local proxy (`stage.foo.redhat.com:1337`).
6867

6968
## Troubleshooting
7069

@@ -154,8 +153,8 @@ In CI (Konflux), tests run with:
154153
export E2E_USER="your-redhat-username"
155154
export E2E_PASSWORD="your-redhat-password"
156155

157-
# Run tests directly against stage
158-
npx playwright test integrations.spec.ts --headed
156+
# Run tests against local dev proxy
157+
npx playwright test integrations-ui.spec.ts --headed
159158

160159
# Watch the tests run in the browser!
161160
```

playwright/global-setup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ async function login(page: Page, user: string, password: string) {
2626
throw new Error('Proxy config incorrect - Lockdown page detected');
2727
}
2828

29-
console.log(`Logging in as ${user}...`);
29+
console.log('Logging in...');
3030

3131
// Fill username
3232
const usernameInput = page

playwright/integrations-ui.spec.ts

Lines changed: 36 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { expect, test } from '@playwright/test';
2-
import { INTEGRATIONS_PATH } from './test-utils';
2+
import { INTEGRATIONS_PATH, dismissCookieConsent } from './test-utils';
33
import { generateCommunicationPayload, generateWebhookPayload } from './utils/data-generators';
44
import {
5+
deleteIntegration,
56
fillCommunicationForm,
67
fillWebhookForm,
78
waitForSuccessNotification,
@@ -23,37 +24,37 @@ import {
2324
test.describe('Integrations Navigation', () => {
2425
test('should navigate across all tabs', async ({ page }) => {
2526
await page.goto(INTEGRATIONS_PATH);
26-
await page.waitForLoadState('networkidle');
27+
await page.waitForLoadState('domcontentloaded');
2728
await expect(page).toHaveURL(/settings\/integrations/);
2829

2930
await test.step('Communications tab loads', async () => {
3031
const communicationsTab = page
3132
.locator('button:has-text("Communications"), a:has-text("Communications")')
3233
.first();
33-
if (await communicationsTab.isVisible()) {
34-
await communicationsTab.click();
35-
await page.waitForLoadState('networkidle');
36-
}
34+
await expect(communicationsTab).toBeVisible({ timeout: 5000 });
35+
await communicationsTab.click();
36+
await page.waitForLoadState('domcontentloaded');
37+
await expect(page).toHaveURL(/communications/);
3738
});
3839

3940
await test.step('Reporting tab loads', async () => {
4041
const reportingTab = page
4142
.locator('button:has-text("Reporting"), a:has-text("Reporting")')
4243
.first();
43-
if (await reportingTab.isVisible()) {
44-
await reportingTab.click();
45-
await page.waitForLoadState('networkidle');
46-
}
44+
await expect(reportingTab).toBeVisible({ timeout: 5000 });
45+
await reportingTab.click();
46+
await page.waitForLoadState('domcontentloaded');
47+
await expect(page).toHaveURL(/reporting/);
4748
});
4849

4950
await test.step('Webhooks tab loads', async () => {
5051
const webhooksTab = page
5152
.locator('button:has-text("Webhooks"), a:has-text("Webhooks")')
5253
.first();
53-
if (await webhooksTab.isVisible()) {
54-
await webhooksTab.click();
55-
await page.waitForLoadState('networkidle');
56-
}
54+
await expect(webhooksTab).toBeVisible({ timeout: 5000 });
55+
await webhooksTab.click();
56+
await page.waitForLoadState('domcontentloaded');
57+
await expect(page).toHaveURL(/webhooks/);
5758
});
5859
});
5960
});
@@ -68,16 +69,9 @@ test.describe('Webhook Integration Lifecycle', () => {
6869

6970
// Navigate to integrations page
7071
await page.goto(INTEGRATIONS_PATH);
71-
await page.waitForLoadState('networkidle');
72+
await page.waitForLoadState('domcontentloaded');
7273

73-
// Dismiss cookie consent if it appears
74-
const acceptCookies = page
75-
.getByRole('button', { name: 'Accept all' })
76-
.or(page.getByRole('button', { name: 'Accept' }));
77-
if (await acceptCookies.isVisible({ timeout: 2000 }).catch(() => false)) {
78-
await acceptCookies.click();
79-
await page.waitForTimeout(500);
80-
}
74+
await dismissCookieConsent(page);
8175

8276
// Click "Create Integration" dropdown button (use .first() as there may be multiple)
8377
const createButton = page.getByRole('button', { name: 'Create Integration' }).first();
@@ -91,58 +85,30 @@ test.describe('Webhook Integration Lifecycle', () => {
9185
await webhooksMenuItem.waitFor({ state: 'visible', timeout: 5000 });
9286
await webhooksMenuItem.click();
9387

94-
// Now the wizard dialog should open
95-
await page.waitForTimeout(1000);
88+
// Wait for wizard dialog to open
89+
await page.waitForSelector('[role="dialog"]', { state: 'visible', timeout: 10000 });
9690

9791
// Fill the webhook form
9892
await fillWebhookForm(page, webhookPayload);
9993
await waitForSuccessNotification(page);
10094

101-
// Modal closes automatically - wait for it
102-
await page.waitForTimeout(2000);
95+
// Wait for modal to close
96+
await page.waitForSelector('[role="dialog"]', { state: 'hidden', timeout: 10000 });
10397

10498
// Navigate to Webhooks tab to verify the webhook appears
10599
const webhooksTab = page.locator('button:has-text("Webhooks"), a:has-text("Webhooks")').first();
106100
await webhooksTab.click();
107-
await page.waitForLoadState('networkidle');
108-
await page.waitForTimeout(1000);
101+
await page.waitForLoadState('domcontentloaded');
109102

110103
// Verify webhook appears in the list
111104
const webhookElement = page.locator(`text="${webhookPayload.name}"`).first();
112105
await expect(webhookElement).toBeVisible({ timeout: 10000 });
113106

114107
// Delete the webhook
115-
const container = page
116-
.locator('[role="row"], [class*="card"]', {
117-
has: page.locator(`text="${webhookPayload.name}"`),
118-
})
119-
.first();
120-
121-
const deleteButton = container
122-
.locator('button[aria-label*="Delete"], button:has-text("Delete")')
123-
.first();
124-
if (await deleteButton.isVisible({ timeout: 3000 }).catch(() => false)) {
125-
await deleteButton.click();
126-
} else {
127-
const kebabButton = container
128-
.locator('button[aria-label*="Actions"], button[aria-label*="Kebab"]')
129-
.first();
130-
await kebabButton.click();
131-
await page.waitForTimeout(500);
132-
const deleteMenuItem = page.locator('button:has-text("Delete")').first();
133-
await deleteMenuItem.click();
134-
}
135-
136-
// Confirm deletion
137-
const confirmButton = page
138-
.locator('button:has-text("Delete"), button:has-text("Confirm")')
139-
.first();
140-
await confirmButton.waitFor({ state: 'visible', timeout: 5000 });
141-
await confirmButton.click({ force: true });
108+
await deleteIntegration(page, webhookPayload.name);
142109

143-
await page.waitForTimeout(2000);
144-
const webhookGone = await webhookElement.isVisible({ timeout: 2000 }).catch(() => false);
145-
expect(webhookGone).toBe(false);
110+
// Verify it's gone
111+
await expect(webhookElement).not.toBeVisible({ timeout: 10000 });
146112
});
147113
});
148114

@@ -156,16 +122,9 @@ test.describe('Communication Integration Lifecycle', () => {
156122

157123
// Navigate to integrations page
158124
await page.goto(INTEGRATIONS_PATH);
159-
await page.waitForLoadState('networkidle');
125+
await page.waitForLoadState('domcontentloaded');
160126

161-
// Dismiss cookie consent if it appears
162-
const acceptCookies = page
163-
.getByRole('button', { name: 'Accept all' })
164-
.or(page.getByRole('button', { name: 'Accept' }));
165-
if (await acceptCookies.isVisible({ timeout: 2000 }).catch(() => false)) {
166-
await acceptCookies.click();
167-
await page.waitForTimeout(500);
168-
}
127+
await dismissCookieConsent(page);
169128

170129
// Click "Create Integration" dropdown button (same pattern as webhook)
171130
const createButton = page.getByRole('button', { name: 'Create Integration' }).first();
@@ -179,61 +138,31 @@ test.describe('Communication Integration Lifecycle', () => {
179138
await communicationsMenuItem.waitFor({ state: 'visible', timeout: 5000 });
180139
await communicationsMenuItem.click();
181140

182-
// Wait for wizard to appear
183-
await page.waitForTimeout(1000);
141+
// Wait for wizard dialog to open
142+
await page.waitForSelector('[role="dialog"]', { state: 'visible', timeout: 10000 });
184143

185-
// Fill the communication form (wizard should be open now)
144+
// Fill the communication form
186145
await fillCommunicationForm(page, payload);
187146
await waitForSuccessNotification(page);
188147

189-
// Modal closes automatically - wait for it
190-
await page.waitForTimeout(2000);
148+
// Wait for modal to close
149+
await page.waitForSelector('[role="dialog"]', { state: 'hidden', timeout: 10000 });
191150

192151
// Navigate to Communications tab to verify the integration appears
193152
const communicationsTab = page
194153
.locator('button:has-text("Communications"), a:has-text("Communications")')
195154
.first();
196155
await communicationsTab.click();
197-
await page.waitForLoadState('networkidle');
198-
await page.waitForTimeout(1000);
156+
await page.waitForLoadState('domcontentloaded');
199157

200158
// Verify integration appears in the list
201159
const integrationElement = page.locator(`text="${payload.name}"`).first();
202160
await expect(integrationElement).toBeVisible({ timeout: 10000 });
203161

204162
// Delete the integration
205-
const container = page
206-
.locator('[role="row"], [class*="card"]', {
207-
has: page.locator(`text="${payload.name}"`),
208-
})
209-
.first();
163+
await deleteIntegration(page, payload.name);
210164

211-
const deleteButton = container
212-
.locator('button[aria-label*="Delete"], button:has-text("Delete")')
213-
.first();
214-
if (await deleteButton.isVisible({ timeout: 3000 }).catch(() => false)) {
215-
await deleteButton.click();
216-
} else {
217-
const kebabButton = container
218-
.locator('button[aria-label*="Actions"], button[aria-label*="Kebab"]')
219-
.first();
220-
await kebabButton.click();
221-
await page.waitForTimeout(500);
222-
const deleteMenuItem = page.locator('button:has-text("Delete")').first();
223-
await deleteMenuItem.click();
224-
}
225-
226-
// Confirm deletion
227-
const confirmButton = page
228-
.locator('button:has-text("Delete"), button:has-text("Confirm")')
229-
.first();
230-
await confirmButton.waitFor({ state: 'visible', timeout: 5000 });
231-
await confirmButton.click({ force: true });
232-
233-
await page.waitForTimeout(2000);
234-
const integrationGone = await integrationElement
235-
.isVisible({ timeout: 2000 })
236-
.catch(() => false);
237-
expect(integrationGone).toBe(false);
165+
// Verify it's gone
166+
await expect(integrationElement).not.toBeVisible({ timeout: 10000 });
238167
});
239168
});

0 commit comments

Comments
 (0)