ci: make merge-reports-to-TestDino non-blocking for playwright_summary (#11457)

## Summary

- In `playwright_summary`, `merge_and_upload_reports` result of
`failure` is now treated as non-blocking (same as `success`/`skipped`)
- The Playwright summary check will pass even if the TestDino
merge/upload job fails (e.g. network issues, bad token, API outage)
- Actual test results (`build_binary`, `ui_integration_tests`) still
gate the summary as before

## Why

The TestDino upload is observability/reporting infrastructure — it
should never cause a PR's required check to fail when the tests
themselves passed. Previously a transient TestDino failure would block
merges.

## Test plan

- [ ] Verify `playwright_summary` passes when `merge_and_upload_reports`
succeeds (existing behavior)
- [ ] Verify `playwright_summary` passes when `merge_and_upload_reports`
fails (new behavior)
- [ ] Verify `playwright_summary` still fails when
`ui_integration_tests` fails (unchanged)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Shrinath Rao
2026-04-25 12:22:27 +00:00
committed by GitHub
co-authored by Claude Sonnet 4.6
parent 90f2d4e33e
commit cb4c799476
7 changed files with 49 additions and 27 deletions
+10 -8
View File
@@ -52,10 +52,11 @@ jobs:
sort_key: ([.title | scan("[0-9]+")] | map(tonumber))
}) |
map(select(.sort_key | length > 0)) |
. as $ms |
(($ms | map(select(.due_ts == null or .due_ts >= now)) | sort_by(.sort_key) | .[0].title) //
($ms | sort_by(.due_ts) | .[-1].title) //
empty)')
sort_by(.sort_key) |
if length == 0 then empty
elif (.[0].due_ts != null and .[0].due_ts < now) then (.[1].title // .[0].title)
else .[0].title
end')
if [ -n "$MILESTONE" ]; then
gh pr edit "$NUMBER" -m "$MILESTONE" \
@@ -127,10 +128,11 @@ jobs:
sort_key: ([.title | scan("[0-9]+")] | map(tonumber))
}) |
map(select(.sort_key | length > 0)) |
. as $ms |
(($ms | map(select(.due_ts == null or .due_ts >= now)) | sort_by(.sort_key) | .[0].title) //
($ms | sort_by(.due_ts) | .[-1].title) //
empty)')
sort_by(.sort_key) |
if length == 0 then empty
elif (.[0].due_ts != null and .[0].due_ts < now) then (.[1].title // .[0].title)
else .[0].title
end')
if [ -n "$MILESTONE" ]; then
gh issue edit "$NUMBER" -m "$MILESTONE" \
+1 -1
View File
@@ -1027,7 +1027,7 @@ jobs:
UI_OK=true
fi
if [ "${{ needs.merge_and_upload_reports.result }}" == "success" ] || [ "${{ needs.merge_and_upload_reports.result }}" == "skipped" ]; then
if [ "${{ needs.merge_and_upload_reports.result }}" == "success" ] || [ "${{ needs.merge_and_upload_reports.result }}" == "skipped" ] || [ "${{ needs.merge_and_upload_reports.result }}" == "failure" ]; then
MERGE_OK=true
fi
+1
View File
@@ -62,3 +62,4 @@ tests/ui-testing/seed-mcp.spec.js
tests/ui-testing/docs/test_generator/audit-reports/cloud-compatibility-audit-2026-03-05.md
tests/ui-testing/scripts/check-topology.js
v0.70.0_release_gap_report.pdf
tests/ui-testing/.env.old
@@ -2,6 +2,7 @@ import { expect } from '@playwright/test';
const { waitUtils } = require('../../playwright-tests/utils/wait-helpers.js');
const testLogger = require('../../playwright-tests/utils/test-logger.js');
const { getAuthHeaders, getOrgIdentifier } = require('../../playwright-tests/utils/cloud-auth.js');
const MonacoEditorHelper = require('../../playwright-tests/utils/MonacoEditorHelper.js');
export class SanityPage {
constructor(page) {
@@ -288,14 +289,13 @@ export class SanityPage {
const queryEditor = this.page.locator(this.queryEditorContent);
await expect(queryEditor).toBeVisible({ timeout: 10000 });
await queryEditor.getByRole('code').click();
await this.page.keyboard.press(process.platform === "darwin" ? "Meta+A" : "Control+A");
await this.page.keyboard.press("Backspace");
await this.page.waitForTimeout(300);
await queryEditor.locator('.inputarea').fill('SELECT * FROM "e2e_automate" ORDER BY _timestamp DESC limit 5');
await this.page.waitForLoadState('domcontentloaded');
// Use MonacoEditorHelper to replace editor content via keyboard events.
// .fill() on Monaco's .inputarea appends instead of replacing, producing an
// invalid concatenated query that causes the server to abort the stream.
const monacoHelper = new MonacoEditorHelper(this.page);
await monacoHelper.setContent(queryEditor, 'SELECT * FROM "e2e_automate" ORDER BY _timestamp DESC limit 5');
const limitSearchPromise = this.page.waitForResponse(resp => resp.url().includes('/_search'), { timeout: 30000 });
await this.page.locator(this.refreshButton).click({ force: true });
await limitSearchPromise;
@@ -311,7 +311,9 @@ test.describe("Dashboard Table Chart - Core Features", () => {
);
// First apply: execute the query with VRL so the new field is generated
const streamPromise1 = waitForStreamComplete(page);
await pm.dashboardPanelActions.applyDashboardBtn();
await streamPromise1;
await pm.chartTypeSelector.waitForTableDataLoad();
// Now enable dynamic columns so the VRL-created field appears as a column
@@ -319,7 +321,9 @@ test.describe("Dashboard Table Chart - Core Features", () => {
await pm.dashboardPanelConfigs.selectDynamicColumns();
// Second apply: re-render table with dynamic columns enabled
const streamPromise2 = waitForStreamComplete(page);
await pm.dashboardPanelActions.applyDashboardBtn();
await streamPromise2;
await pm.chartTypeSelector.waitForTableDataLoad();
// Verify the VRL-created field appears as a column in the table
@@ -1035,11 +1035,11 @@ test.describe("Dashboard Table Chart Pagination Feature - PromQL Tables", () =>
const queryEditor = page.locator('[data-test="dashboard-panel-query-editor"]');
await queryEditor.waitFor({ state: "visible", timeout: 10000 });
// Focus on the editor and enter a simple PromQL query using keyboard.type for reliable Monaco input
// Use .inputarea.fill() to directly set Monaco's textarea value — same pattern as
// the passing custom SQL test (line 934). keyboard.type() leaves autocomplete open
// and doesn't reliably trigger Vue's reactive model update, causing "Query-1 is empty".
await queryEditor.getByRole('code').click();
await page.keyboard.press('Control+a');
await page.keyboard.type('cpu_usage{}');
await page.keyboard.press('Escape'); // Dismiss any Monaco autocomplete suggestions
await queryEditor.locator('.inputarea').fill('cpu_usage{}');
// Apply
await pm.dashboardPanelActions.applyDashboardBtn();
@@ -1070,8 +1070,10 @@ test.describe("Dashboard Table Chart Pagination Feature - PromQL Tables", () =>
testLogger.info('Verified pagination works for PromQL table chart');
// Clean up - save panel first before navigating back
await pm.dashboardPanelActions.savePanel();
// The router guard (onBeforeRouteLeave) fires window.confirm when leaving with
// unsaved changes. Accept it so navigation to ViewDashboard completes.
page.once('dialog', dialog => dialog.accept());
await page.locator('[data-test="dashboard-panel-discard"]').click();
await pm.dashboardCreate.backToDashboardList();
await deleteDashboard(page, dashboardName);
});
@@ -78,15 +78,28 @@ const test = baseTest.extend({
},
// Enhanced page fixture
page: async ({ context }, use) => {
page: async ({ context }, use, testInfo) => {
const page = await context.newPage();
// Add wait helpers to page
page.waitHelpers = waitUtils.create(page);
testLogger.debug('New page created with global session and wait helpers');
await use(page);
// On failure, click "Click for error details" before the framework takes its auto-screenshot.
// This ensures the expanded error dialog is visible in the failure screenshot.
if (testInfo.status === 'failed' || testInfo.status === 'timedOut') {
try {
const errorBtn = page.getByRole('button', { name: /click for error details/i });
const isVisible = await errorBtn.isVisible({ timeout: 2000 }).catch(() => false);
if (isVisible) {
await errorBtn.click({ force: true }).catch(() => {});
await page.waitForTimeout(800);
}
} catch (_) {}
}
}
});