Repository navigation
feat: Web console: run the e2e tests on embedded clusters, modernize them, and cover more of the console - #20544
vogievetsky wants to merge 32 commits into
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- run the e2e tests with @playwright/test (playwright.config.ts) instead of Jest and the playwright-chromium library: the page fixture replaces the per-spec browser setup, a global setup waits for the console, failed tests keep a screenshot and a trace, and expect().toPass() replaces the retry helpers - use locators (getByRole, form groups found by their label) instead of XPath, page.$ and waitForSelector - open the Datasources and Tasks views filtered to the test's datasource, so the tests work on a cluster with more than a page of datasources or tasks - fail a query attempt as soon as it errors, so that it can be retried while the Broker catches up Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- wait for tasks to finish and segments to load by querying sys.tasks and sys.segments (util/sql.ts) instead of reading the Tasks and Datasources views by column position - read the console's tables by their column headers (extractTableRecords), and use it for the remaining checks of the Datasources and Tasks views in the tutorial test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A filter on a value with a #, ? or | in it (like a link from a datasource to its tasks or segments) filtered on the wrong thing: - TableFilters.toString() did not encode # and ?, which end the hash route, so the rest of the value was dropped; now they are encoded (and decoded) like & % and / - | separates a filter's values and could not be escaped, so a value with a | was split into several; now \| and \\ stand for a literal | and \ in a value, and TableFilters.eq and the filter menu of a table cell filter on their value as one value Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Now that table filters in the URL can hold any value, open the Datasources and Tasks views filtered on the test's full datasource name, rather than on the part of it that a filter could hold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rather than run sed and examples/bin/post-index-task (bash and Python, with the Coordinator hard-coded on :8081), read the tutorial ingestion spec, set the datasource name (and interval) on it, POST it through the console's service with the request fixture, and poll the task's status, failing with the task's error if it fails. The spec's input now points at this checkout's tutorial directory rather than one relative to where Druid runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Api.encodePath left ^ and | as they are, which the browser sends as is and Jetty rejects with "400 Illegal Path Character", so every action on a datasource with one of those in its name (mark unused, kill, retention rules, compaction config) failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The newDatasourceName fixture makes a unique datasource name and, after the test passes, shuts down the datasource's tasks, deletes its compaction config, marks its segments unused and kills them. After a failure the datasource is kept to look into, and the log says which. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- merge component/query into component/workbench (both drove the Query view) - plain object types for data (rows read from views, what to set in forms) instead of classes using Object.assign and interface merging, so the no-unsafe-declaration-merging eslint override goes; union types with functions for the partitions specs and data connectors - the data loader is a loadData(page, config) function with one flat config instead of a class with a config object per step Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds WebConsoleTestBase and CoreWebConsoleTest to embedded-tests (tag web-console, profile web-console-tests), which start an embedded cluster with a Router and run the Playwright specs on it, with a keep-alive mode for working on a spec. Fixes two races in the specs that this surfaced: reading a view's table before it loaded, and canceling a query before it was sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
S3WebConsoleTest runs the new s3-ingestion spec on an embedded cluster with an S3 container (also its deep storage and task log storage), loading the tutorial file through the data loader's Amazon S3 connector. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
web-checks.sh installs web-console with the console built, then runs the web-console-tests profile of embedded-tests (building only the modules it needs) instead of building a distribution and starting it with script/druid. worker.yml uploads the failsafe reports and the Playwright results in place of the distribution's logs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nctionality Splits CoreWebConsoleTest into a *WebConsoleTest per functionality (indexing, compact, msq, query) and moves S3WebConsoleTest to the s3 package. WebConsoleTestBase stays in console, as shared infrastructure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y spec is run The specs no longer delete their datasources on an embedded cluster, as it's thrown away after the test class. WebConsoleSpecsTest checks that every spec of web-console/e2e-tests is run by a *WebConsoleTest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
showStep(page, title) saves a screenshot to e2e-tests/steps/<spec>-NNN.png (git ignored) when E2E_SHOW_STEPS=true, and is called by the page objects at each step of the data loader, each view read, query results and dialogs, and by a fixture as the test ends. setQueryInput closes the autocomplete that typing opens, as it covered the query. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
input-formats.spec.ts loads CSV, TSV, Parquet, ORC and Avro OCF files through the data loader, checking the input format it picks and the columns it parses, run by InputFormatsWebConsoleTest (with the Avro, Parquet and ORC extensions) next to ITLocalInputSourceAllInputFormatTest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kafka-ingestion.spec.ts sets up a Kafka supervisor through the data loader (as in the Kafka tutorial), then suspends, resumes and terminates it from the Supervisors view, run by KafkaWebConsoleTest with a Kafka container next to the other Kafka embedded tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sql-ingestion.spec.ts loads data through the SQL data loader, then ingests and reindexes with REPLACE in the Query view, run by SqlIngestionWebConsoleTest next to the MSQ ingestion embedded tests. WorkbenchOverview gets runIngestQuery, and waits for the query's own request before its result, as the view shows the last result until then. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
datasource-actions.spec.ts marks a datasource's segments unused, used again, then deletes them with a kill task from the Datasources view, run by DatasourceActionsWebConsoleTest. DatasourcesOverview can show unused datasources and run and confirm a datasource's actions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
retention-rules.spec.ts edits a datasource's retention rules from the Datasources view (loadForever, then dropForever, which drops the data), run by RetentionRulesWebConsoleTest. jdbc-lookup.spec.ts initializes the lookups and adds a JDBC lookup from the Lookups view, then queries it, run by JdbcLookupWebConsoleTest (with the table in the Derby metadata store, as JdbcLookupTest does). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dart.spec.ts runs a query with the Dart engine picked in the Query view, follows it in the current Dart queries panel (with its details), and cancels a running Dart query from the panel, run by DartWebConsoleTest. The embedded clusters keep the reports of finished Dart queries, as script/druid build does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… header fails Addresses the CodeQL alert (potential input resource leak): the FileInputStream was created inside the GZIPInputStream wrapper, whose constructor reads the header and can throw before the try-with-resources has a resource to close. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… to see an e2e test fail in CI Temporary, to be reverted: the SQL data loader test of sql-ingestion.spec.ts (run by SqlIngestionWebConsoleTest) clicks 'Start loading data', which this renames, so it should time out looking for the button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FrankChen021
left a comment
There was a problem hiding this comment.
🟡 Changes recommended
The current head cannot pass the new SQL ingestion E2E because the helper searches for a button label the application does not render. Additional issues can produce stale query assertions, indefinite E2E hangs, and incorrect filtering for datasource names containing |.
Reviewed 79 of 79 changed files.
| Severity | Findings |
|---|---|
| P0 | 0 |
| P1 | 2 |
| P2 | 2 |
| P3 | 0 |
| Total | 4 |
This is an automated review by Codex GPT-5.6-Luna(max)
After addressing the findings or replying to the comments, you can request another review from me to trigger a new automated review.
| await clickButton(destinationDialog, 'Save'); | ||
| await destinationDialog.waitFor({ state: 'detached' }); | ||
| await showStep(page, 'SQL data loader: Schema'); | ||
| await clickButton(schemaStep.locator('.prev-next-bar'), 'Start loading data'); |
There was a problem hiding this comment.
[P1] Use the SQL loader's rendered submit label
Finding: The schema step renders the exact button name Start ingesting data, but this helper still searches for Start loading data. Because clickButton uses an exact accessible-name match, sql-ingestion.spec.ts times out before starting ingestion, so SqlIngestionWebConsoleTest cannot pass at this head.
Suggestion: Update the helper to the rendered label, or revert the temporary UI rename before merging.
There was a problem hiding this comment.
That commit was a deliberate, temporary failure to see how an e2e failure shows in CI; it's reverted in a20cb85.
| await this.submit(query, options); | ||
|
|
||
| const error = this.page.locator('.execution-error-pane'); | ||
| await done.or(error).waitFor({ timeout: QUERY_TIMEOUT }); |
There was a problem hiding this comment.
[P1] Wait for the current ingestion result
Finding: The workbench keeps the previous successful execution visible while a new query loads, so done.or(error).waitFor() can resolve on the old .ingest-success-pane. The second runIngestQuery in sql-ingestion.spec.ts can therefore return the first query's text before the new task finishes.
Suggestion: Wait for the current execution to complete or verify a query-specific state transition before reading the result pane.
There was a problem hiding this comment.
Right, the view can show the last output while the next query runs; fixed in 09872dc by clearing the output before each query.
| new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8) | ||
| )) { | ||
| String line; | ||
| while ((line = reader.readLine()) != null) { |
There was a problem hiding this comment.
[P2] Enforce the spec timeout while consuming output
Finding: The runner drains process.getInputStream() to EOF before calling waitFor with SPEC_TIMEOUT_MINUTES. A hung Playwright process can keep stdout open, so the timeout branch is never reached and CI can hang until an external timeout.
Suggestion: Consume output asynchronously while waiting, then destroy the process and join or close the reader when the timeout expires.
There was a problem hiding this comment.
Fixed in d8a3312: the output is read on its own thread, and a timed-out spec's process tree is killed.
| this.key = key; | ||
| this.mode = mode; | ||
| this.values = typeof value === 'string' ? value.split('|') : value; | ||
| this.values = typeof value === 'string' ? TableFilter.splitNeedle(value) : value; |
There was a problem hiding this comment.
[P2] Preserve literal pipes in datasource filters
Finding: String values are now parsed with splitNeedle, where an unescaped | means OR. Existing datasource links in segments-view.tsx and datasources-view.tsx still pass datasource names as strings, so a valid datasource named a|b becomes two filter values instead of one literal datasource.
Suggestion: Use the constructor's literal-value form for those datasource links and add a pipe-containing datasource regression case.
There was a problem hiding this comment.
Good catch: fixed in 6be4e1f; the segment timeline's two links now pass the datasource as a literal value.
… button, to see an e2e test fail in CI" This reverts commit 97edd15, which was only to see how an e2e test failure shows in CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… views' filters The segment timeline's links built their datasource filter from the name as a needle, where | separates values, so a datasource named a|b filtered on a or b. They now pass it as one literal value, as TableFilters.eq does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tests The view keeps the last query's output (even after a reload) and can show it while it runs the next query, until that one shows progress or a result, so it could be read as the next query's output. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The output was read to its end before the timeout was checked, and a hung spec keeps its output open, so the timeout couldn't hold. The output is now read on its own thread, and on a timeout the process and its descendants (npx runs Playwright in a child process) are killed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FrankChen021
left a comment
There was a problem hiding this comment.
🟡 Changes recommended
This follow-up review covers the current head after rechecking all four prior review threads. Those prior findings appear resolved at the current head.
One remaining P2 should be addressed before merging: the new literal table-filter serialization is still passed as a datasource identifier to the Datasources and Segments segment-timeline views. A datasource containing | or \\ therefore cannot be loaded correctly in the timeline.
Reviewed all 80 changed files in the full diff, the 5 files in the incremental diff since the previous review, and the relevant surrounding call sites.
Validation: the exact GitHub base/head mergeability check and git diff --check passed. No builds or tests were run; this was a static review.
| Severity | Findings |
|---|---|
| P0 | 0 |
| P1 | 0 |
| P2 | 1 |
| P3 | 0 |
| Total | 1 |
This is an automated review by Codex GPT-5.6 Luna(Max)
After addressing the findings or replying to the comments, you can request another review from me to trigger a new automated review.
|
|
||
| public get value(): string { | ||
| return this.values.join('|'); | ||
| return TableFilter.joinNeedle(this.values); |
There was a problem hiding this comment.
[P2] Keep literal datasource names raw for timeline consumers
Finding: The new getter serializes literal values for the table-filter grammar (a|b becomes a\|b). However, the existing Datasources and Segments views still pass filter.value directly to SegmentTimeline as a datasource name. With the new literal representation, a datasource containing | or \ is sent to the timeline with its escape characters, so the SQL/API timeline query cannot find the real datasource.
Suggestion: Use the raw single value (filter.values[0]) at those timeline call sites, or expose separate raw-value and serialized-needle accessors so consumers do not treat the escaped grammar value as a datasource identifier.
There was a problem hiding this comment.
Good catch, fixed in e7bc667: the segment timeline now gets the filter's raw value (values[0]), only when there's exactly one.
…rces and Segments views' filters Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FrankChen021
left a comment
There was a problem hiding this comment.
🟡 Changes recommended
Reviewed the full 84-file merge-base diff for apache/druid#20544. I rechecked the prior review thread: the datasource and segments timeline issue is resolved at e7bc667 by passing the raw datasource filter value when there is exactly one literal datasource filter.
Coverage included the CI and Maven integration, the embedded JUnit harness, all new WebConsoleTest classes, the Playwright specs and shared utilities/page objects, table-filter/API serialization changes, and the processing async-resource changes plus production consumers. The remaining issue is the removal of fail-fast completion from CollectAsyncResource, which regresses current master behavior for failed asynchronous downloads.
Validation: git diff --check 165e9b9 e7bc667 passed. No test suite was run; this review used static inspection of the prepared checkout.
| Severity | Findings |
|---|---|
| P0 | 0 |
| P1 | 1 |
| P2 | 0 |
| P3 | 0 |
| Total | 1 |
Findings that could not be attached inline:
-
processing/src/main/java/org/apache/druid/common/asyncresource/CollectAsyncResource.java:83 - [P1] Preserve fail-fast completion for collected resources
Finding: This change removes the fail-fast path that is present on current master: each source callback now only increments readyCount, so if one source fails while another remains pending, the collected resource stays pending and its consumer does not observe the failure until every source completes. PartialQueryableIndexCursorFactory relies on the collected resource callback to call failHolder and release the query's download state; a failed deep-storage range can therefore leave the query waiting until timeout and keep other downloads running.
Suggestion: Retain the failed-source get()/setException path, guarded so only the first failure completes the target, and keep a regression test that verifies a failed source completes the collection without waiting for pending sources.
This is an automated review by Codex GPT-5.6 Luna(Max)
After addressing the findings or replying to the comments, you can request another review from me to trigger a new automated review.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@FrankChen021 re the The CodeQL resource-leak alert on |
FrankChen021
left a comment
There was a problem hiding this comment.
🟢 Approval recommended
The current head is functionally ready to merge. One non-blocking documentation issue remains: the embedded-cluster table mixes runner mappings with long test descriptions under the wrong column.
Reviewed 80 of 80 changed files; one non-blocking finding remains.
Validation: git diff --check 4c4ad2c376fc66fa5d9de200caa663f551919f8e...HEAD passed. Tests, build, typecheck, and formatter were not run; the prepared worktree has no node_modules.
| Severity | Findings |
|---|---|
| P0 | 0 |
| P1 | 0 |
| P2 | 0 |
| P3 | 1 |
| Total | 1 |
This is an automated review by Codex GPT-5.6-Luna(max)
After addressing the findings or replying to the comments, you can request another review from me to trigger a new automated review.
| | `retention-rules.spec.ts` | `server.RetentionRulesWebConsoleTest` | | ||
| | `jdbc-lookup.spec.ts` | `lookup.JdbcLookupWebConsoleTest` | | ||
| | `dart.spec.ts` | `msq.DartWebConsoleTest` | | ||
| | `input-formats.spec.ts` | Loads the same 10 rows (over 2 days) of Wikipedia edits from local CSV, TSV, Parquet, ORC and Avro OCF files through the data loader, one test per format. Checks that the **Parse data** step picks the right input format by itself and parses the columns, and that the datasource ends up as 2 segments with 10 rows. Skipped without `DRUID_E2E_TEST_DATA_DIR` (the data files of embedded-tests): it runs on an embedded cluster with the Avro, Parquet and ORC extensions, from `InputFormatsWebConsoleTest`. | |
There was a problem hiding this comment.
[P3] Keep the embedded-test table columns consistent
Finding: The table beginning at line 66 declares its second column as Run by, but lines 80-86 append long What it does descriptions as additional rows without a new header. These rows render in the wrong column and duplicate several spec entries, making the embedded-test mapping misleading.
Suggestion: Move the descriptions under a separate Spec | What it does table or remove the duplicate block so the embedded-cluster table contains only runner mappings.
Note from the human
So the e2e tests in the web console have not been touched in a long time. On a whim I asked Claude "If I was added e2e tests today would they be written differently"... and boy did it have a lot to say.
While Claude was clauding I described what I was doing to @gianm and he said: "It would be cool if web console e2e tests used the embedded tests framework" and the rest is history...
Description
The web console's end-to-end tests ran in CI by building a full Druid distribution, starting it with
script/druid, running the Playwright specs against it and stopping it. That was slow, the specs shared one long-lived cluster (so leftovers from one test could break another), and the console flows lived apart from the Druid features they exercise.This PR runs the specs on the embedded clusters that the rest of Druid's tests use (
embedded-tests), modernizes the specs themselves, and adds console coverage for features that the embedded tests already cover through the API. Each console test lives next to the embedded tests of its feature (for examples3/S3WebConsoleTestnext toS3StorageTest), so the "load data from S3" flow is tested through the console too.Running the console e2e specs on embedded clusters
WebConsoleTestBase(inembedded-tests, packageorg.apache.druid.testing.embedded.console) starts an embedded cluster with a Router, which serves the console from theweb-consolejar, and runs a Playwright spec ofweb-console/e2e-testson it withnpx playwright test <spec>. A subclass adds the resources the spec needs (S3 or Kafka containers, extensions, properties) inaddResources/configureClusterand passes their settings to the spec as environment variables. The JUnit failure message carries Playwright's output; each spec writes its screenshots and traces to its ownweb-console/test-results/<spec>/.web-console: excluded from the default surefire run and run by failsafe with the newweb-console-testsprofile:-Dweb.console.keepAlive=truekeeps a test's cluster up and prints the command to run its spec against it (with--ui/--debugas you like).-Dweb.console.port=18081runs the specs against the dev server (npm start) instead of the bundled console.DRUID_E2E_TEST_CLUSTER_IS_DISPOSABLE).WebConsoleSpecsTest(a plain unit test) fails if a spec ofweb-console/e2e-testsisn't run by any*WebConsoleTest, or if one runs a spec that doesn't exist.npm run test-e2e(script/druidstays for that); the ones that need a resource (S3, Kafka, extra extensions, a lookup table) skip themselves there.CI
.github/scripts/web-checks.shinstallsweb-console(with the console built) and then runs./mvnw verify -pl 'embedded-tests,!web-console' -am -Pweb-console-tests -DskipUTs, which builds only the modulesembedded-testsneeds: no distribution, no extensions download, no separate JVMs to start.worker.ymluploadsembedded-tests/target/failsafe-reports/(the Druid and Playwright logs) andweb-console/test-results/instead of the distribution's logs, and the existing JUnit report step now shows each console test.Modernized e2e specs
The specs were written in a Puppeteer style; they now use Playwright as intended:
@playwright/testrunner (playwright.config.ts) instead of Jest: fixtures instead of per-spec browser setup, aglobalSetupthat waits for the cluster,expect.poll/toPassinstead of a home-made retry, screenshots, traces and anerror-context.mdon failure.getByRole, form fields found by their label) instead of XPath and element handles.post-index-task.Object.assigndata classes), withloadData(page, config)for the data loader.E2E_SHOW_STEPS=truesaves a screenshot of each step a test takes toe2e-tests/steps/<spec>-NNN.png, a quick way to see what a test does.web-console/e2e-tests/README.mddescribes the tests, how to run them both ways, and how to write one.Running them on embedded clusters also surfaced three latent races in the specs that the quickstart's timing had hidden (reading a view's table before it loaded, cancelling a query before it was sent, reading the Query view's previous result), now fixed.
New console coverage
s3-ingestion.spec.tss3.S3WebConsoleTestinput-formats.spec.tsindexer.InputFormatsWebConsoleTestkafka-ingestion.spec.tsindexing.KafkaWebConsoleTestsql-ingestion.spec.tsmsq.SqlIngestionWebConsoleTestREPLACEfrom external data and from a datasource in the Query viewdatasource-actions.spec.tsindexing.DatasourceActionsWebConsoleTestretention-rules.spec.tsserver.RetentionRulesWebConsoleTestloadForever, thendropForever)jdbc-lookup.spec.tslookup.JdbcLookupWebConsoleTestdart.spec.tsmsq.DartWebConsoleTestThe existing specs (
tutorial-batch,reindexing,auto-compaction,multi-stage-query,cancel-query) run fromindexing.BatchIndexingWebConsoleTest,compact.AutoCompactionWebConsoleTest,msq.MultiStageQueryWebConsoleTestandquery.SqlQueryCancelWebConsoleTest.Web console fixes found on the way
#datasources/datasource=...) couldn't hold values with#or?(which end the hash route) or|(which separates values), so going from a datasource with one of those in its name to its tasks or segments filtered on the wrong thing.#,?,&,/and%are now encoded, and\|/\\escape|/\in a value.Api.encodePathleft^and|as is, which Jetty rejects (400 Illegal Path Character), so every action on a datasource with one of those in its name (mark unused, kill, retention rules, compaction config) failed.Release note
Fixed the web console's handling of datasource names containing
#,?,|,^or\: links between views now filter on the right datasource, and actions such as marking segments unused, killing data, and editing retention rules or compaction configs no longer fail for such names.Key changed/added classes in this PR
WebConsoleTestBase,WebConsoleSpecsTestand the*WebConsoleTestclasses (embedded-tests)embedded-tests/pom.xml(web-console-testsprofile),.github/scripts/web-checks.sh,.github/workflows/worker.ymlweb-console/e2e-tests/**,web-console/playwright.config.tsTableFilter,TableFilters,Api.encodePath(web console)This PR has:
web-console/e2e-tests/README.md).table-filter.spec.ts,table-filters.spec.ts,hash-routing.spec.ts,api.spec.ts).