@@ -83,28 +83,27 @@ def setUpClass(cls) -> None:
8383 REPO_ROOT / "tools" / "web" / "README.md"
8484 ).read_text (encoding = "utf-8" )
8585
86- def test_readme_identifies_the_exact_hardware_tested_public_beta (self ) -> None :
86+ def test_readme_identifies_the_qualified_v060_release (self ) -> None :
8787 firmware = markdown_section (self .readme , "What works" )
8888 normalized = " " .join (firmware .split ())
8989
9090 self .assertIn (
91- "public browser installer currently offers the exact v0.4.2 hardware-tested beta " ,
91+ "qualified public v0.6.0 release " ,
9292 normalized ,
9393 )
94- self .assertIn ("Production Chrome erase/install" , firmware )
95- self .assertIn ("interrupted-flash recovery passed" , firmware )
96- self .assertIn ("Complete release qualification continues" , firmware )
94+ self .assertIn ("all five exact release profiles" , normalized )
95+ self .assertIn ("four ESP profiles use Web Serial" , normalized )
96+ self .assertIn ("verified UF2" , firmware )
97+ self .assertIn ("BOOTSEL" , firmware )
9798 self .assertIn ("`esp32-4mb`" , firmware )
98- self .assertIn ("Classic ESP32, 4 MiB external SPI flash" , firmware )
9999 self .assertIn ("`esp32-s3-n16r8`" , firmware )
100- self .assertIn ("16 MiB flash / 8 MiB Octal PSRAM" , firmware )
101- self .assertIn (
102- "v0.4.2 hardware-tested beta; browser install/recovery passed" ,
103- firmware ,
104- )
105- self .assertIn ("Planned; unavailable" , firmware )
106- self .assertNotIn ("currently offers qualified images" , firmware )
107- self .assertNotIn ("full HIL pending" , firmware )
100+ self .assertIn ("`waveshare-esp32-s3-lcd-147b`" , firmware )
101+ self .assertIn ("`esp32-c3-4mb`" , firmware )
102+ self .assertIn ("`rpi-pico2-w`" , firmware )
103+ self .assertEqual (firmware .count ("Qualified v0.6.0" ), 5 )
104+ self .assertNotIn ("v0.4.2 hardware-tested beta" , firmware )
105+ self .assertNotIn ("Planned; unavailable" , firmware )
106+ self .assertNotIn ("in-progress `0.6.0`" , firmware )
108107
109108 def test_readme_caption_describes_only_the_visible_app (self ) -> None :
110109 caption_start = self .readme .index ("<em>Actual PyBLE app" )
@@ -116,23 +115,22 @@ def test_readme_caption_describes_only_the_visible_app(self) -> None:
116115 self .assertIn ("generated MicroPython" , caption )
117116 self .assertNotRegex (caption , r"(?i)pictured|board|module" )
118117
119- def test_readme_try_steps_use_the_hardware_tested_beta_safely (self ) -> None :
118+ def test_readme_try_steps_use_the_qualified_v060_release_safely (self ) -> None :
120119 try_section = markdown_section (self .readme , "Try PyBLE" )
121120 normalized = " " .join (try_section .split ())
122121
123- self .assertIn ("v0.4.2 hardware-tested beta " , normalized )
124- self .assertIn (
125- "Browser installation and interrupted-flash recovery passed" ,
126- normalized ,
127- )
128- self .assertIn ("complete release qualification continues " , normalized )
129- self .assertNotIn ( "full HIL pending " , try_section )
122+ self .assertIn ("qualified v0.6.0 release " , normalized )
123+ self .assertIn ("four ESP profiles" , normalized )
124+ self . assertIn ( "Web Serial" , try_section )
125+ self . assertIn ( "Pico 2 W" , try_section )
126+ self . assertIn ( "verified UF2" , try_section )
127+ self .assertIn ("BOOTSEL " , try_section )
128+ self .assertIn ( "iPadOS cannot perform " , normalized )
130129 self .assertNotIn ("use it at your own risk" , try_section )
131130 self .assertIn ("exact profile" , try_section )
132131 self .assertIn ("back up" , try_section )
133- self .assertIn ("enabled install action" , try_section )
134132 self .assertIn ("Flashing erases the board" , try_section )
135- self .assertNotIn ("wait for that page " , try_section . lower () )
133+ self .assertNotIn ("v0.4.2 hardware-tested beta " , try_section )
136134
137135 def test_current_public_surfaces_agree_on_beta_and_c3_state (self ) -> None :
138136 combined = "\n " .join (
@@ -232,7 +230,31 @@ def test_post_release_attestation_bounds_the_completed_hil_scope(self) -> None:
232230
233231 def test_public_surfaces_link_the_release_evidence_and_changelog (self ) -> None :
234232 self .assertIn (
235- "(docs/validation/browser-flashing/v0.4.2-production.md)" ,
233+ "https://pyble.dev/firmware/v0.6.0/release.json" ,
234+ self .readme ,
235+ )
236+ self .assertIn (
237+ "https://pyble.dev/firmware/v0.6.0/RELEASE_NOTES.md" ,
238+ self .readme ,
239+ )
240+ self .assertIn (
241+ "https://pyble.dev/firmware/v0.6.0/SHA256SUMS" ,
242+ self .readme ,
243+ )
244+ self .assertIn (
245+ "https://pyble.dev/firmware/v0.6.0/RECOVERY.md" ,
246+ self .readme ,
247+ )
248+ self .assertIn (
249+ "https://github.com/PyBLE-dev/PyBLE/tree/firmware-v0.6.0" ,
250+ self .readme ,
251+ )
252+ self .assertIn (
253+ "c2940281a14feddb55c48de15ac18087e9317d1b7130e514fab5a209b046a1e6" ,
254+ self .readme ,
255+ )
256+ self .assertIn (
257+ "0c7230d6708797c241160ba71fbd37e6b22f180a" ,
236258 self .readme ,
237259 )
238260 self .assertIn (
@@ -249,6 +271,36 @@ def test_public_surfaces_link_the_release_evidence_and_changelog(self) -> None:
249271 self .assertIn ("complete release qualification remains pending" , release )
250272 self .assertNotIn ("qualified release" , release )
251273
274+ v060 = markdown_section (
275+ self .changelog , "Firmware 0.6.0 — 2026-08-21"
276+ )
277+ for wording in (
278+ "qualified five-profile release" ,
279+ "`esp32-4mb`" ,
280+ "`esp32-s3-n16r8`" ,
281+ "`waveshare-esp32-s3-lcd-147b`" ,
282+ "`esp32-c3-4mb`" ,
283+ "`rpi-pico2-w`" ,
284+ "firmware-v0.6.0" ,
285+ "0c7230d6708797c241160ba71fbd37e6b22f180a" ,
286+ "MicroPython v1.28.0" ,
287+ "ESP-IDF v5.5.1" ,
288+ "four ESP profiles" ,
289+ "verified UF2" ,
290+ "BOOTSEL" ,
291+ "all five HIL rows passed" ,
292+ "https://pyble.dev/flash" ,
293+ ):
294+ self .assertIn (wording , v060 )
295+
296+ unreleased = markdown_section (self .changelog , "Unreleased" )
297+ for stale in (
298+ "Abandoned the unpublished local `firmware-v0.6.0` candidate" ,
299+ "Pico remains absent from public release metadata" ,
300+ "all candidate/HIL gates restart" ,
301+ ):
302+ self .assertNotIn (stale , unreleased )
303+
252304 def test_public_specifications_describe_the_exact_beta_without_overclaim (
253305 self ,
254306 ) -> None :
0 commit comments