Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 99 additions & 21 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,33 +98,111 @@ jobs:
if: steps.changed-files.outputs.any_changed == 'true'
working-directory: ./smoke-tests/otel-collector
run: bats .
app-smoke-test:
name: HyperDX App Smoke Test
runs-on: ubuntu-latest
e2e-tests:
name: End-to-End Tests
runs-on: ubuntu-24.04
timeout-minutes: 15
container:
image: mcr.microsoft.com/playwright:v1.55.0-jammy
permissions:
contents: read
pull-requests: write
contents: write

steps:
- name: Checkout
id: checkout
uses: actions/checkout@v4
- name: Waiting for vercel preview to be ready
uses: patrickedqvist/[email protected]
id: waitFor200

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache-dependency-path: 'yarn.lock'
cache: 'yarn'

- name: Install dependencies
run: yarn install

- name: Build dependencies
run: npx nx run-many -t ci:build

- name: Run Playwright tests
run: |
cd packages/app
yarn test:e2e

- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: always()
with:
token: ${{ secrets.GITHUB_TOKEN }}
max_timeout: 1200
check_interval: 10
- run: echo ${{steps.waitFor200.outputs.url}}
name: playwright-report
path: packages/app/playwright-report/
retention-days: 30

- name: Stably Runner Action
uses: stablyhq/stably-runner-action@v3
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
test-suite-id: cmc548u5u0001la04q7y8ddj2
github-token: ${{ secrets.GITHUB_TOKEN }}
api-key: ${{ secrets.STABLY_API_KEY }}
environment: PRODUCTION
variable-overrides: |
{
"SITE_URL": "${{ steps.waitFor200.outputs.url }}"
name: test-results
path: packages/app/test-results/
retention-days: 30

- name: Generate test results message
id: test-results
if: always() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
result-encoding: string
script: |
const fs = require('fs');
const path = require('path');

try {
const resultsPath = path.join('packages/app/test-results/results.json');
if (fs.existsSync(resultsPath)) {
const results = JSON.parse(fs.readFileSync(resultsPath, 'utf8'));
const { stats } = results;

const failed = stats.unexpected || 0;
const passed = stats.expected || 0;
const flaky = stats.flaky || 0;
const skipped = stats.skipped || 0;
const duration = Math.round((stats.duration || 0) / 1000);

const summary = failed > 0
? `❌ **${failed} test${failed > 1 ? 's' : ''} failed**`
: `✅ **All tests passed**`;

return `## E2E Test Results

${summary} • ${passed} passed • ${skipped} skipped • ${duration}s

| Status | Count |
|--------|-------|
| ✅ Passed | ${passed} |
| ❌ Failed | ${failed} |
| ⚠️ Flaky | ${flaky} |
| ⏭️ Skipped | ${skipped} |

[View full report →](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
} else {
return `## E2E Test Results

❌ **Test results file not found**

[View full report →](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
}
} catch (error) {
console.log('Could not parse test results:', error.message);
return `## E2E Test Results

❌ **Error reading test results**

[View full report →](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})`;
}

- name: Comment PR with test results
uses: mshick/add-pr-comment@v2
if: always() && github.event_name == 'pull_request'
with:
message: ${{ steps.test-results.outputs.result }}
message-id: e2e-test-results
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ e2e/cypress/screenshots/
e2e/cypress/videos/
e2e/cypress/results

# playwright
**/test-results/
**/playwright-report/
**/playwright/.cache/

# scripts
scripts/*.csv
**/venv
Expand Down
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ dev-unit:
ci-unit:
npx nx run-many -t ci:unit

.PHONY: e2e
e2e:
@if [ -z "$(tags)" ]; then \
echo "Running all E2E tests in local mode..."; \
cd packages/app && yarn test:e2e; \
else \
echo "Running E2E tests with tags: $(tags)"; \
cd packages/app && yarn test:e2e --grep "$(tags)"; \
fi

# TODO: check db connections before running the migration CLIs
.PHONY: dev-migrate-db
dev-migrate-db:
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
"packageManager": "[email protected]",
"resolutions": {
"@types/react": "18.3.1",
"@types/react-dom": "18.3.1"
"@types/react-dom": "18.3.1",
"@types/express": "4.17.21",
"@types/express-serve-static-core": "4.17.43"
}
}
4 changes: 2 additions & 2 deletions packages/api/src/routers/external-api/v2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import rateLimiter from '@/utils/rateLimiter';

const router = express.Router();

const rateLimiterKeyGenerator = (req: express.Request) => {
return req.headers.authorization || req.ip;
const rateLimiterKeyGenerator = (req: express.Request): string => {
return req.headers.authorization ?? req.ip ?? 'unknown';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linting failure fix

};

const defaultRateLimiter = rateLimiter({
Expand Down
10 changes: 10 additions & 0 deletions packages/app/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,15 @@ module.exports = {
],
},
},
{
// Disable strict rules for E2E test files
files: ['tests/e2e/**/*.ts', 'tests/e2e/**/*.js'],
rules: {
'no-console': 'off',
'no-empty': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@next/next/no-html-link-for-pages': 'off',
},
},
],
};
4 changes: 4 additions & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
"ci:lint": "yarn lint && yarn tsc --noEmit && yarn lint:styles --quiet",
"ci:unit": "jest --ci --coverage",
"dev:unit": "jest --watchAll --detectOpenHandles",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:debug": "playwright test --debug",
"storybook": "storybook dev -p 6006",
"storybook:build": "storybook build",
"knip": "knip"
Expand Down Expand Up @@ -104,6 +107,7 @@
"@chromatic-com/storybook": "^1.5.0",
"@hookform/devtools": "^4.3.1",
"@jedmao/location": "^3.0.0",
"@playwright/test": "^1.47.0",
"@storybook/addon-essentials": "^8.1.5",
"@storybook/addon-interactions": "^8.1.5",
"@storybook/addon-links": "^8.1.5",
Expand Down
59 changes: 59 additions & 0 deletions packages/app/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { defineConfig, devices } from '@playwright/test';

/**
* @see https://playwright.dev/docs/test-configuration
*/
export default defineConfig({
testDir: './tests/e2e',
/* Global setup to ensure server is ready */
globalSetup: require.resolve('./global-setup.js'),
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 1,
/* Use multiple workers on CI for faster execution */
workers: undefined,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allows playwright to use as many cores that are available

/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [
['html'],
['json', { outputFile: 'test-results/results.json' }],
...(process.env.CI ? [['github', {}] as const] : []),
],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:8080',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
/* Take screenshot on failure */
screenshot: 'only-on-failure',
/* Record video on failure */
video: 'retain-on-failure',
},

/* Global test timeout - increased from default 30s to 60s to reduce flaky test failures */
timeout: 60 * 1000,

/* Configure projects for different test environments */
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can add more browsers later

],

/* Run your local dev server before starting the tests */
webServer: {
command:
'NEXT_PUBLIC_IS_LOCAL_MODE=true NEXT_TELEMETRY_DISABLED=1 yarn run dev',
port: 8080,
reuseExistingServer: !process.env.CI,
timeout: 180 * 1000,
stdout: 'pipe',
stderr: 'pipe',
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

run server in local-only mode for simplicty

});
3 changes: 3 additions & 0 deletions packages/app/src/AutocompleteInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default function AutocompleteInput({
showHotkey,
onSubmit,
queryHistoryType,
'data-testid': dataTestId,
}: {
inputRef: React.RefObject<HTMLInputElement>;
value?: string;
Expand All @@ -42,6 +43,7 @@ export default function AutocompleteInput({
language?: 'sql' | 'lucene';
showHotkey?: boolean;
queryHistoryType?: string;
'data-testid'?: string;
}) {
const suggestionsLimit = 10;

Expand Down Expand Up @@ -242,6 +244,7 @@ export default function AutocompleteInput({
className="border-0 fs-8"
value={value}
size={size}
data-testid={dataTestId}
onChange={e => onChange(e.target.value)}
onFocus={() => {
setSelectedAutocompleteIndex(-1);
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/DBDashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,7 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
language="lucene"
placeholder="Search your events w/ Lucene ex. column:foo"
enableHotkey
data-testid="search-input"
/>
)
}
Expand Down Expand Up @@ -1047,6 +1048,7 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
) : null}
</Box>
<Button
data-testid="add-new-tile-button"
variant="outline"
mt="sm"
color={dashboard?.tiles.length === 0 ? 'green' : 'dark.3'}
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/DBSearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,7 @@ function DBSearchPage() {
name="source"
onCreate={openNewSourceModal}
allowedSourceKinds={[SourceKind.Log, SourceKind.Trace]}
data-testid="source-selector"
/>
<span className="ms-1">
<SourceSchemaPreview
Expand Down Expand Up @@ -1444,6 +1445,7 @@ function DBSearchPage() {
placeholder="Search your events w/ Lucene ex. column:foo"
queryHistoryType={QUERY_LOCAL_STORAGE.SEARCH_LUCENE}
enableHotkey
data-testid="search-input"
/>
}
/>
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/SearchInputV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export default function SearchInputV2({
onSubmit,
additionalSuggestions,
queryHistoryType,
'data-testid': dataTestId,
...props
}: {
tableConnections?: TableConnection | TableConnection[];
Expand All @@ -47,6 +48,7 @@ export default function SearchInputV2({
onSubmit?: () => void;
additionalSuggestions?: string[];
queryHistoryType?: string;
'data-testid'?: string;
} & UseControllerProps<any>) {
const {
field: { onChange, value },
Expand Down Expand Up @@ -95,6 +97,7 @@ export default function SearchInputV2({
onLanguageChange={onLanguageChange}
onSubmit={onSubmit}
queryHistoryType={queryHistoryType}
data-testid={dataTestId}
aboveSuggestions={
<>
<div className="text-muted fs-8 fw-bold me-1">Searching for:</div>
Expand Down
Loading
Loading