Skip to content

Commit 83b9c8a

Browse files
authored
chore: Add playwright tests for app in local-only mode (#1181)
Fixes: HDX-2442
1 parent 5a44953 commit 83b9c8a

28 files changed

+1613
-49
lines changed

.github/workflows/main.yml

Lines changed: 99 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -98,33 +98,111 @@ jobs:
9898
if: steps.changed-files.outputs.any_changed == 'true'
9999
working-directory: ./smoke-tests/otel-collector
100100
run: bats .
101-
app-smoke-test:
102-
name: HyperDX App Smoke Test
103-
runs-on: ubuntu-latest
101+
e2e-tests:
102+
name: End-to-End Tests
103+
runs-on: ubuntu-24.04
104+
timeout-minutes: 15
105+
container:
106+
image: mcr.microsoft.com/playwright:v1.55.0-jammy
104107
permissions:
108+
contents: read
105109
pull-requests: write
106-
contents: write
110+
107111
steps:
108112
- name: Checkout
109-
id: checkout
110113
uses: actions/checkout@v4
111-
- name: Waiting for vercel preview to be ready
112-
uses: patrickedqvist/[email protected]
113-
id: waitFor200
114+
115+
- name: Setup Node.js
116+
uses: actions/setup-node@v4
117+
with:
118+
node-version-file: '.nvmrc'
119+
cache-dependency-path: 'yarn.lock'
120+
cache: 'yarn'
121+
122+
- name: Install dependencies
123+
run: yarn install
124+
125+
- name: Build dependencies
126+
run: npx nx run-many -t ci:build
127+
128+
- name: Run Playwright tests
129+
run: |
130+
cd packages/app
131+
yarn test:e2e
132+
133+
- name: Upload Playwright report
134+
uses: actions/upload-artifact@v4
135+
if: always()
114136
with:
115-
token: ${{ secrets.GITHUB_TOKEN }}
116-
max_timeout: 1200
117-
check_interval: 10
118-
- run: echo ${{steps.waitFor200.outputs.url}}
137+
name: playwright-report
138+
path: packages/app/playwright-report/
139+
retention-days: 30
119140

120-
- name: Stably Runner Action
121-
uses: stablyhq/stably-runner-action@v3
141+
- name: Upload test results
142+
uses: actions/upload-artifact@v4
143+
if: always()
122144
with:
123-
test-suite-id: cmc548u5u0001la04q7y8ddj2
124-
github-token: ${{ secrets.GITHUB_TOKEN }}
125-
api-key: ${{ secrets.STABLY_API_KEY }}
126-
environment: PRODUCTION
127-
variable-overrides: |
128-
{
129-
"SITE_URL": "${{ steps.waitFor200.outputs.url }}"
145+
name: test-results
146+
path: packages/app/test-results/
147+
retention-days: 30
148+
149+
- name: Generate test results message
150+
id: test-results
151+
if: always() && github.event_name == 'pull_request'
152+
uses: actions/github-script@v7
153+
with:
154+
result-encoding: string
155+
script: |
156+
const fs = require('fs');
157+
const path = require('path');
158+
159+
try {
160+
const resultsPath = path.join('packages/app/test-results/results.json');
161+
if (fs.existsSync(resultsPath)) {
162+
const results = JSON.parse(fs.readFileSync(resultsPath, 'utf8'));
163+
const { stats } = results;
164+
165+
const failed = stats.unexpected || 0;
166+
const passed = stats.expected || 0;
167+
const flaky = stats.flaky || 0;
168+
const skipped = stats.skipped || 0;
169+
const duration = Math.round((stats.duration || 0) / 1000);
170+
171+
const summary = failed > 0
172+
? `❌ **${failed} test${failed > 1 ? 's' : ''} failed**`
173+
: `✅ **All tests passed**`;
174+
175+
return `## E2E Test Results
176+
177+
${summary} • ${passed} passed • ${skipped} skipped • ${duration}s
178+
179+
| Status | Count |
180+
|--------|-------|
181+
| ✅ Passed | ${passed} |
182+
| ❌ Failed | ${failed} |
183+
| ⚠️ Flaky | ${flaky} |
184+
| ⏭️ Skipped | ${skipped} |
185+
186+
[View full report →](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
187+
} else {
188+
return `## E2E Test Results
189+
190+
❌ **Test results file not found**
191+
192+
[View full report →](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
193+
}
194+
} catch (error) {
195+
console.log('Could not parse test results:', error.message);
196+
return `## E2E Test Results
197+
198+
❌ **Error reading test results**
199+
200+
[View full report →](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
130201
}
202+
203+
- name: Comment PR with test results
204+
uses: mshick/add-pr-comment@v2
205+
if: always() && github.event_name == 'pull_request'
206+
with:
207+
message: ${{ steps.test-results.outputs.result }}
208+
message-id: e2e-test-results

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ e2e/cypress/screenshots/
5757
e2e/cypress/videos/
5858
e2e/cypress/results
5959

60+
# playwright
61+
**/test-results/
62+
**/playwright-report/
63+
**/playwright/.cache/
64+
6065
# scripts
6166
scripts/*.csv
6267
**/venv

Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ dev-unit:
5959
ci-unit:
6060
npx nx run-many -t ci:unit
6161

62+
.PHONY: e2e
63+
e2e:
64+
@if [ -z "$(tags)" ]; then \
65+
echo "Running all E2E tests in local mode..."; \
66+
cd packages/app && yarn test:e2e; \
67+
else \
68+
echo "Running E2E tests with tags: $(tags)"; \
69+
cd packages/app && yarn test:e2e --grep "$(tags)"; \
70+
fi
71+
6272
# TODO: check db connections before running the migration CLIs
6373
.PHONY: dev-migrate-db
6474
dev-migrate-db:

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
"packageManager": "[email protected]",
5353
"resolutions": {
5454
"@types/react": "18.3.1",
55-
"@types/react-dom": "18.3.1"
55+
"@types/react-dom": "18.3.1",
56+
"@types/express": "4.17.21",
57+
"@types/express-serve-static-core": "4.17.43"
5658
}
5759
}

packages/api/src/routers/external-api/v2/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import rateLimiter from '@/utils/rateLimiter';
99

1010
const router = express.Router();
1111

12-
const rateLimiterKeyGenerator = (req: express.Request) => {
13-
return req.headers.authorization || req.ip;
12+
const rateLimiterKeyGenerator = (req: express.Request): string => {
13+
return req.headers.authorization ?? req.ip ?? 'unknown';
1414
};
1515

1616
const defaultRateLimiter = rateLimiter({

packages/app/.eslintrc.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,15 @@ module.exports = {
4444
],
4545
},
4646
},
47+
{
48+
// Disable strict rules for E2E test files
49+
files: ['tests/e2e/**/*.ts', 'tests/e2e/**/*.js'],
50+
rules: {
51+
'no-console': 'off',
52+
'no-empty': 'off',
53+
'@typescript-eslint/no-explicit-any': 'off',
54+
'@next/next/no-html-link-for-pages': 'off',
55+
},
56+
},
4757
],
4858
};

packages/app/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
"ci:lint": "yarn lint && yarn tsc --noEmit && yarn lint:styles --quiet",
1818
"ci:unit": "jest --ci --coverage",
1919
"dev:unit": "jest --watchAll --detectOpenHandles",
20+
"test:e2e": "playwright test",
21+
"test:e2e:ui": "playwright test --ui",
22+
"test:e2e:debug": "playwright test --debug",
2023
"storybook": "storybook dev -p 6006",
2124
"storybook:build": "storybook build",
2225
"knip": "knip"
@@ -104,6 +107,7 @@
104107
"@chromatic-com/storybook": "^1.5.0",
105108
"@hookform/devtools": "^4.3.1",
106109
"@jedmao/location": "^3.0.0",
110+
"@playwright/test": "^1.47.0",
107111
"@storybook/addon-essentials": "^8.1.5",
108112
"@storybook/addon-interactions": "^8.1.5",
109113
"@storybook/addon-links": "^8.1.5",

packages/app/playwright.config.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { defineConfig, devices } from '@playwright/test';
2+
3+
/**
4+
* @see https://playwright.dev/docs/test-configuration
5+
*/
6+
export default defineConfig({
7+
testDir: './tests/e2e',
8+
/* Global setup to ensure server is ready */
9+
globalSetup: require.resolve('./global-setup.js'),
10+
/* Run tests in files in parallel */
11+
fullyParallel: true,
12+
/* Fail the build on CI if you accidentally left test.only in the source code. */
13+
forbidOnly: !!process.env.CI,
14+
/* Retry on CI only */
15+
retries: process.env.CI ? 2 : 1,
16+
/* Use multiple workers on CI for faster execution */
17+
workers: undefined,
18+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
19+
reporter: [
20+
['html'],
21+
['json', { outputFile: 'test-results/results.json' }],
22+
...(process.env.CI ? [['github', {}] as const] : []),
23+
],
24+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
25+
use: {
26+
/* Base URL to use in actions like `await page.goto('/')`. */
27+
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:8080',
28+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
29+
trace: 'on-first-retry',
30+
/* Take screenshot on failure */
31+
screenshot: 'only-on-failure',
32+
/* Record video on failure */
33+
video: 'retain-on-failure',
34+
},
35+
36+
/* Global test timeout - increased from default 30s to 60s to reduce flaky test failures */
37+
timeout: 60 * 1000,
38+
39+
/* Configure projects for different test environments */
40+
projects: [
41+
{
42+
name: 'chromium',
43+
use: {
44+
...devices['Desktop Chrome'],
45+
},
46+
},
47+
],
48+
49+
/* Run your local dev server before starting the tests */
50+
webServer: {
51+
command:
52+
'NEXT_PUBLIC_IS_LOCAL_MODE=true NEXT_TELEMETRY_DISABLED=1 yarn run dev',
53+
port: 8080,
54+
reuseExistingServer: !process.env.CI,
55+
timeout: 180 * 1000,
56+
stdout: 'pipe',
57+
stderr: 'pipe',
58+
},
59+
});

packages/app/src/AutocompleteInput.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default function AutocompleteInput({
2525
showHotkey,
2626
onSubmit,
2727
queryHistoryType,
28+
'data-testid': dataTestId,
2829
}: {
2930
inputRef: React.RefObject<HTMLInputElement>;
3031
value?: string;
@@ -42,6 +43,7 @@ export default function AutocompleteInput({
4243
language?: 'sql' | 'lucene';
4344
showHotkey?: boolean;
4445
queryHistoryType?: string;
46+
'data-testid'?: string;
4547
}) {
4648
const suggestionsLimit = 10;
4749

@@ -242,6 +244,7 @@ export default function AutocompleteInput({
242244
className="border-0 fs-8"
243245
value={value}
244246
size={size}
247+
data-testid={dataTestId}
245248
onChange={e => onChange(e.target.value)}
246249
onFocus={() => {
247250
setSelectedAutocompleteIndex(-1);

packages/app/src/DBDashboardPage.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,7 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
944944
language="lucene"
945945
placeholder="Search your events w/ Lucene ex. column:foo"
946946
enableHotkey
947+
data-testid="search-input"
947948
/>
948949
)
949950
}
@@ -1047,6 +1048,7 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
10471048
) : null}
10481049
</Box>
10491050
<Button
1051+
data-testid="add-new-tile-button"
10501052
variant="outline"
10511053
mt="sm"
10521054
color={dashboard?.tiles.length === 0 ? 'green' : 'dark.3'}

0 commit comments

Comments
 (0)