A node-graph editor: 193 block types, edges, handles, sub-block inputs and subflow containers
A workflow canvas SDODS cannot drag
What makes it hard
This is the hardest UI surface in the product and the one the framework serves worst.
- Node elements carry a uuid. A test that has just added a block can only find it by diffing the node list before and after, which is a race dressed as an assertion.
- The sub-block inputs — short text, long text, dropdown, combobox, code — carry neither an id nor a test id. Only the wrapper is addressable, so every field fill degrades to a descendant CSS guess like
[data-nodeid] [data-subblock-id] input. - The single most important control in the product, Run, was addressable only through a product-tour attribute. A tour refactor would have removed it silently.
- The two subflow containers, loop and parallel, have no selector of their own and their nesting is invisible in the DOM entirely.
- 48 OAuth connectors have no obtainable grant in CI, so the only assertable thing about them is the connect affordance.
And 193 block types is not a number you write 193 scenarios for.
How to cover it
Say the honest thing first: SDODS ships no canvas, drag or graph steps. There is no I drag ... onto ..., no handle-to-handle connect step, no node-count assertion, and BasePage has no drag helper either. A design exists on the roadmap; it is not in the library today.
What you can do, in the order I would do it:
- Add through the palette, not through drag. Clicking a palette item adds the block on this product, so
I click the element with test id "toolbar-block-<type>"covers the whole add case with a shared step — provided the palette items carry test ids. That single request is worth more than a drag step. - Assert the graph through the API. The canvas is a view over a document. Seed the workflow with
I seed via POSTand assert the persisted shape with JSON path and a schema. Nesting that is invisible in the DOM is perfectly visible in the saved graph, and an API assertion cannot be flaky about a node that has not finished animating. - Ask for one attribute.
data-blocktypealongside the uuid removes the diff-the-node-list class across all 193 types at once. It is the cheapest testability change in the whole backlog. - Keep drag in a page object. If you genuinely need the drag interaction — and for most coverage you do not — write it as a project step over Playwright's
dragToinside a page object with a heal context, and give it a phrasing in the house style so it reads like the shared steps around it.
Also worth asking for: a serialisable projection of the block registry. A parity check between "what the palette offers" and "what the registry declares" is a good test, and it should not require importing an application module that pulls in icons and a feature-flag system.
Scenario Outline over a generated table, plus a deny-direction outline for the types that must not be offered on this plan or in this tab, is the whole catalogue.Scenario sketch
@hybrid @regression
Scenario: A block added on the canvas is persisted into the graph
Given I use a leased user with role "member"
When I seed via POST "/workflows" with body:
"""json
{ "name": "sdods-canvas-{{runId}}", "workspaceId": "{{fixtureWorkspaceId}}" }
"""
And I save the response JSON path "id" as "workflowId"
And I register cleanup DELETE "/workflows/{{workflowId}}"
And I navigate to the "workflow" page
And I click the element with test id "toolbar-block-condition"
And I click the element with test id "run-workflow"
Then the element with test id "block-error-message" should be visible
@api @regression @data-driven
Scenario Outline: The palette offers exactly what the registry declares
When I send a GET request to "/internal/blocks/registry"
Then the response status should be 200
And the response JSON path "$..[?(@.type=='<type>')].addable" should equal "<addable>"
Examples:
| type | addable |
| condition | true |
| starter | false |The first scenario is @hybrid on purpose: it seeds through the API so the browser starts on a workflow that exists, which is both faster and the only version of it that is deterministic. Drag is absent from both sketches because there is no step for it — not because the interaction does not matter.
Other use cases
- Testing an eight-screen auth funnel that has no test idsLogin, signup, email verification, 2FA, the device flow, OAuth consent, invitation acceptance and password reset
- A 403 that a shared cache is free to replayAuthorisation refusals on an API served through a CDN
- Plan caps, spending limits, and a gate that never firedBilling, usage and spending-limit routes behind plan entitlements
- Ten scenarios said "Forbidden" and meant "you are out of workflows"A shared fixture workspace on a free plan with a three-workflow cap, holding twenty-three