self_test_bridge 2.3.1
self_test_bridge: ^2.3.1 copied to clipboard
The socket and the report server for self_test: drives a running Flutter app from outside it, and serves each run as a local web page.
2.3.1 - 2026-10-15 #
tapwaits for its widget to stop moving before it acts (SelfTestManager.tap(settleFirst: true)). A client taps straight after anexiststhat answered while a route was still sliding in, and that tap was lost: on a slower emulator a Dismantle button opened nothing.self_testis now^2.3.1, the first version withsettleFirst.
2.3.0 - 2026-09-22 #
findanswers through [SelfTestManager.tryFind] instead of catching [WidgetNotFoundError] and throwing it away. The error builds its candidate list eagerly, a full walk of the tree, and this command never read the message: measured on a real Sites list, a miss cost 12.1s against 0.2s for a hit. A client that walks indices until it runs out paid that on every walk, which is what made a list-ordering case slower than the code it replaced. Still notfindAll, which would drop the index the caller sent.- The importer reads which keys a step may carry from [StepVocabulary.keysFor] rather than from a list kept here. The same list now backs the published schema, so the schema and the refusal cannot disagree.
- Breaking for the constraint, not for the API:
self_testis now^2.3.0.keysForandtryFindare both 2.3.0, and a resolve against 2.2.0 would compile against neither.
2.2.0 - 2026-09-22 #
getFacts,setFactsandclearFacts: the values a run captured, read and seeded over the socket, so the half of an end-to-end case that judges a value can be another process. A non-string fact is refused rather than stringified.- A recorded step keeps a locator's
withinscope. - The report page carries a scoped locator through an edit instead of dropping it, and shows the scope in the one-line rendering of a step.
- Breaking for the constraint, not for the API:
self_testis now^2.2.0, not^2.0.0. The facts commands callSelfTestManager.facts,seedFactsandforgetFacts, which 2.2.0 introduced, so the old constraint said a resolution would work that cannot compile.
2.1.0 - 2026-09-22 #
Everything here was found while driving two real apps against a live tenant, and every entry carries the test that pins it.
The tap recorder can be switched on over the wire #
recordScenarioStart and recordScenarioStop turn on the app's own recorder,
the one that watches pointer events at the root and resolves each one against
the element tree. That is the part a driver cannot do for itself: a driven tap
already knows the locator it was given, so recording it proves nothing, and
only a tap nobody named exercises the recorder at all. Until now the recorder
could be reached only from the in-app panel, which a headless machine has no
way to press.
Starting a second recording while one is open is refused rather than accepted,
because a replay that runs while a recording is open feeds itself into that
recording and the run still reports a pass. recordScenarioStop answers with
the step count, since a recording that captured nothing looks exactly like a
successful one from the outside.
A wait can watch what the app draws #
wait with exists, absent, gone or not_exists now polls a locator
instead of reading the registered-widget map. That map is filled by wrapping
widgets in SelfTestableWidget, so an app that is only ever driven from
outside could wait for exactly one thing: a fixed number of milliseconds. A
fixed pause long enough for the worst run is spent on every good one, and it is
still a guess, which is how a slow screen turns into a red run and how the next
person ends up raising the number.
Running out of budget throws. Answering {'success': false} would have been
read as a pass, because the step reader only looks at passed, result and
error.
A wait may name the condition it already means #
saveScript refuses a wait step that names a condition a stored script
cannot watch, and says so with the assertion to write instead. duration is
accepted as harmless company, because it is the one condition a script can
carry out and it is read from the same field either way. Refusing it too would
have rejected scripts that were already correct.
A step whose value would be dropped is refused by name #
saveScript read a step's value from value, text or duration and
silently dropped every other key. A pause written {"action":"wait","ms":6000}
imported with no value at all and replayed as the 500ms default: the scenario
asked for six seconds, got half of one, and still reported green. Measured:
2.34s wall clock against 8.51s for the same pause written the documented way.
Importing such a step now fails and names the key. mockChannel is exempt on
purpose, because it validates its own spec.
A pause standing in front of an assertion is that assertion's budget #
Pinned with a test rather than changed. Folding the pause into the assertion that follows it is what makes a lengthened assertion cheap, and withdrawing it would make every fold in the suite silently spend its time twice.
2.0.0 - 2026-09-16 #
First release on pub.dev. Before this the bridge was publish_to: none and a
consumer had to name a git dependency, which is a poor answer for the half of
self_test that carries the socket, the report page and everything the command
line talks to.
Fixed #
-
A back press at the root no longer closes the app.
goBackanswered an empty navigator withSystemNavigator.pop(). On iOS that does nothing, which is why it survived; on Android it puts the activity behind the launcher, and the bridge lives inside the app, so leaving the app takes the bridge with it. Every scenario opens with five or sixgoBacks, so the first one backgrounded the app and the next command timed out against a socket nobody was left to answer, while the pid, the port andadb forwardall still looked healthy. Nothing left to pop is now a no-op reported aspopped: false. -
The runs list no longer squashes the scripts list. With
flex: 0 1 autothe runs pane, which grows all day, pushed the scripts pane down to a third of its own content, and three scripts already had to be scrolled. -
A step that names no widget is shown without a locator line, instead of an empty one.
Added #
-
The report page shows what the app threw during a run, under the step that caused it and in the exported HTML, apart from the step's own error. A replay refusing and an app breaking are different failures, and reading one as the other sends a reader looking for a widget that was never the problem.
-
An imported script may carry a compound
mockChannelstep, so one step can answer a plugin whose generated channel differs per platform. -
assertAboveis accepted by the importer and drawn in thecheckfamily.
Changed #
- The page builds its colours from
SelfTestPalette, which the in-app panel also uses, so a run reads the same in a browser and on the device. A test pins the two together.
1.0.0 #
Shipped alongside self_test 1.0.0, from git rather than from pub.dev: the WebSocket bridge, the stored-script commands, and the report server with its page, export and script editor.