Repository: denoland/fresh
Pull request #2901: tests: use the same browser instance across tests and better use explicit resource management
Author: login: iuioiua
Created: 2025-05-07T23:21:54Z | Merged: 2025-05-21T09:32:19Z

On my machine, reduces time taken to perform `deno task test` from ~1m20s to ~30s.

Inspired by and supersedes #2110.


Diff:
diff --git a/deno.json b/deno.json
index 3c82edd261b..9f447e64c11 100644
--- a/deno.json
+++ b/deno.json
@@ -67,7 +67,7 @@
     "@std/semver": "jsr:@std/semver@1",
     "@std/streams": "jsr:@std/streams@1",
 
-    "@astral/astral": "jsr:@astral/astral@^0.5.2",
+    "@astral/astral": "jsr:@astral/astral@^0.5.3",
     "@marvinh-test/fresh-island": "jsr:@marvinh-test/fresh-island@^0.0.1",
     "linkedom": "npm:linkedom@^0.18.10",
     "@std/async": "jsr:@std/async@^1.0.13",
diff --git a/deno.lock b/deno.lock
index 12bf16d955b..d69dc56de2d 100644
--- a/deno.lock
+++ b/deno.lock
@@ -1,10 +1,10 @@
 {
   "version": "5",
   "specifiers": {
-    "jsr:@astral/astral@~0.5.2": "0.5.3",
-    "jsr:@deno-library/progress@^1.5.1": "1.5.1",
     "jsr:@deno/cache-dir@0.14": "0.14.0",
     "jsr:@deno/doc@0.172": "0.172.0",
+    "jsr:@deno/graph@0.86": "0.86.9",
+    "jsr:@deno/graph@~0.82.3": "0.82.3",
     "jsr:@deno/otel@*": "0.0.2",
     "jsr:@fresh/core@^2.0.0-alpha.29": "2.0.0-alpha.34",
     "jsr:@fresh/plugin-tailwind@^0.0.1-alpha.7": "0.0.1-alpha.7",
@@ -12,38 +12,29 @@
     "jsr:@marvinh-test/fresh-island@*": "0.0.1",
     "jsr:@marvinh-test/fresh-island@^0.0.1": "0.0.1",
     "jsr:@std/assert@0.221": "0.221.0",
-    "jsr:@std/assert@^1.0.13": "1.0.13",
-    "jsr:@std/async@1": "1.0.13",
     "jsr:@std/async@^1.0.13": "1.0.13",
     "jsr:@std/bytes@^1.0.2": "1.0.6",
     "jsr:@std/bytes@^1.0.5": "1.0.6",
     "jsr:@std/cli@^1.0.17": "1.0.17",
     "jsr:@std/collections@^1.0.11": "1.1.0",
-    "jsr:@std/collections@^1.1.0": "1.1.0",
     "jsr:@std/crypto@1": "1.0.5",
     "jsr:@std/crypto@^1.0.4": "1.0.5",
-    "jsr:@std/data-structures@^1.0.8": "1.0.8",
     "jsr:@std/datetime@~0.225.2": "0.225.4",
     "jsr:@std/encoding@1": "1.0.10",
     "jsr:@std/encoding@^1.0.10": "1.0.10",
     "jsr:@std/encoding@^1.0.5": "1.0.10",
-    "jsr:@std/expect@^1.0.16": "1.0.16",
     "jsr:@std/fmt@1": "1.0.8",
-    "jsr:@std/fmt@1.0.3": "1.0.3",
     "jsr:@std/fmt@^1.0.3": "1.0.8",
     "jsr:@std/fmt@^1.0.7": "1.0.8",
     "jsr:@std/fmt@^1.0.8": "1.0.8",
     "jsr:@std/front-matter@^1.0.5": "1.0.9",
     "jsr:@std/fs@1": "1.0.17",
-    "jsr:@std/fs@^1.0.17": "1.0.17",
+    "jsr:@std/fs@^1.0.16": "1.0.17",
     "jsr:@std/fs@^1.0.6": "1.0.17",
     "jsr:@std/html@1": "1.0.4",
     "jsr:@std/html@^1.0.4": "1.0.4",
     "jsr:@std/http@^1.0.15": "1.0.16",
-    "jsr:@std/internal@^1.0.6": "1.0.7",
-    "jsr:@std/internal@^1.0.7": "1.0.7",
-    "jsr:@std/io@0.225": "0.225.0",
-    "jsr:@std/io@0.225.0": "0.225.0",
+    "jsr:@std/io@0.225": "0.225.2",
     "jsr:@std/json@^1.0.2": "1.0.2",
     "jsr:@std/jsonc@1": "1.0.2",
     "jsr:@std/media-types@1": "1.1.0",
@@ -57,11 +48,9 @@
     "jsr:@std/semver@1": "1.0.5",
     "jsr:@std/streams@1": "1.0.9",
     "jsr:@std/streams@^1.0.9": "1.0.9",
-    "jsr:@std/testing@^1.0.12": "1.0.12",
-    "jsr:@std/toml@^1.0.3": "1.0.6",
+    "jsr:@std/toml@^1.0.3": "1.0.5",
     "jsr:@std/uuid@^1.0.7": "1.0.7",
     "jsr:@std/yaml@^1.0.5": "1.0.6",
-    "jsr:@zip-js/zip-js@^2.7.52": "2.7.61",
     "npm:@opentelemetry/api@1": "1.9.0",
     "npm:@opentelemetry/api@^1.9.0": "1.9.0",
     "npm:@opentelemetry/sdk-trace-base@1": "1.30.1_@opentelemetry+api@1.9.0",
@@ -76,8 +65,8 @@
     "npm:esbuild@0.25.4": "0.25.4",
     "npm:github-slugger@2": "2.0.0",
     "npm:linkedom@~0.18.10": "0.18.10",
-    "npm:marked-mangle@^1.1.9": "1.1.10_marked@15.0.11",
-    "npm:marked@^15.0.11": "15.0.11",
+    "npm:marked-mangle@^1.1.9": "1.1.10_marked@15.0.12",
+    "npm:marked@^15.0.11": "15.0.12",
     "npm:postcss@8.4.35": "8.4.35",
     "npm:preact-render-to-string@^6.5.11": "6.5.13_preact@10.26.6",
     "npm:preact@^10.22.0": "10.26.6",
@@ -87,38 +76,29 @@
     "npm:ts-morph@^25.0.1": "25.0.1"
   },
   "jsr": {
-    "@astral/astral@0.5.3": {
-      "integrity": "d6a4628313d8be99aac0f51005c1dc090fa3b4c6b5c8335c26a52d4842aa1276",
-      "dependencies": [
-        "jsr:@deno-library/progress",
-        "jsr:@std/async@1",
-        "jsr:@std/fs@1",
-        "jsr:@std/path@1",
-        "jsr:@zip-js/zip-js"
-      ]
-    },
-    "@deno-library/progress@1.5.1": {
-      "integrity": "966611826b8bb27baae73ab1c4fa4317cd4edd2abb99750cd6f8488d22d5b121",
-      "dependencies": [
-        "jsr:@std/fmt@1.0.3",
-        "jsr:@std/io@0.225.0"
-      ]
-    },
     "@deno/cache-dir@0.14.0": {
       "integrity": "729f0b68e7fc96443c09c2c544b830ca70897bdd5168598446d752f7a4c731ad",
       "dependencies": [
+        "jsr:@deno/graph@0.86",
         "jsr:@std/fmt@^1.0.3",
         "jsr:@std/fs@^1.0.6",
-        "jsr:@std/io@0.225",
+        "jsr:@std/io",
         "jsr:@std/path@^1.0.8"
       ]
     },
     "@deno/doc@0.172.0": {
       "integrity": "72a68ed533576a06feb930a84784ad9ba6d83ca9d581fc734d498c58e32b7cf5",
       "dependencies": [
-        "jsr:@deno/cache-dir"
+        "jsr:@deno/cache-dir",
+        "jsr:@deno/graph@~0.82.3"
       ]
     },
+    "@deno/graph@0.82.3": {
+      "integrity": "5c1fe944368172a9c87588ac81b82eb027ca78002a57521567e6264be322637e"
+    },
+    "@deno/graph@0.86.9": {
+      "integrity": "c4f353a695bcc5246c099602977dabc6534eacea9999a35a8cb24e807192e6a1"
+    },
     "@deno/otel@0.0.2": {
       "integrity": "4ef61b7eb1c4063f8224d66fc43f25e428a566d2e18785d0dc67bb70a318f0ff",
       "dependencies": [
@@ -142,6 +122,7 @@
         "jsr:@std/path@1",
         "jsr:@std/semver",
         "npm:@opentelemetry/api@^1.9.0",
+        "npm:@preact/signals@^2.0.4",
         "npm:esbuild-wasm@0.23.1",
         "npm:esbuild@0.23.1",
         "npm:preact-render-to-string",
@@ -177,12 +158,6 @@
     "@std/assert@0.221.0": {
       "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a"
     },
-    "@std/assert@1.0.13": {
-      "integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29",
-      "dependencies": [
-        "jsr:@std/internal@^1.0.6"
-      ]
-    },
     "@std/async@1.0.13": {
       "integrity": "1d76ca5d324aef249908f7f7fe0d39aaf53198e5420604a59ab5c035adc97c96"
     },
@@ -198,25 +173,12 @@
     "@std/crypto@1.0.5": {
       "integrity": "0dcfbb319fe0bba1bd3af904ceb4f948cde1b92979ec1614528380ed308a3b40"
     },
-    "@std/data-structures@1.0.8": {
-      "integrity": "2fb7219247e044c8fcd51341788547575653c82ae2c759ff209e0263ba7d9b66"
-    },
     "@std/datetime@0.225.4": {
       "integrity": "682bc21738b941a4ed1465be6da01704e8010a3a6d9b615de9458202b84e00ec"
     },
     "@std/encoding@1.0.10": {
       "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1"
     },
-    "@std/expect@1.0.16": {
-      "integrity": "ceeef6dda21f256a5f0f083fcc0eaca175428b523359a9b1d9b3a1df11cc7391",
-      "dependencies": [
-        "jsr:@std/assert@^1.0.13",
-        "jsr:@std/internal@^1.0.7"
-      ]
-    },
-    "@std/fmt@1.0.3": {
-      "integrity": "97765c16aa32245ff4e2204ecf7d8562496a3cb8592340a80e7e554e0bb9149f"
-    },
     "@std/fmt@1.0.8": {
       "integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7"
     },
@@ -249,13 +211,10 @@
         "jsr:@std/streams@^1.0.9"
       ]
     },
-    "@std/internal@1.0.7": {
-      "integrity": "39eeb5265190a7bc5d5591c9ff019490bd1f2c3907c044a11b0d545796158a0f"
-    },
-    "@std/io@0.225.0": {
-      "integrity": "c1db7c5e5a231629b32d64b9a53139445b2ca640d828c26bf23e1c55f8c079b3",
+    "@std/io@0.225.2": {
+      "integrity": "3c740cd4ee4c082e6cfc86458f47e2ab7cb353dc6234d5e9b1f91a2de5f4d6c7",
       "dependencies": [
-        "jsr:@std/bytes@^1.0.2"
+        "jsr:@std/bytes@^1.0.5"
       ]
     },
     "@std/json@1.0.2": {
@@ -276,7 +235,7 @@
     "@std/path@0.221.0": {
       "integrity": "0a36f6b17314ef653a3a1649740cc8db51b25a133ecfe838f20b79a56ebe0095",
       "dependencies": [
-        "jsr:@std/assert@0.221"
+        "jsr:@std/assert"
       ]
     },
     "@std/path@1.0.9": {
@@ -291,20 +250,10 @@
         "jsr:@std/bytes@^1.0.5"
       ]
     },
-    "@std/testing@1.0.12": {
-      "integrity": "fec973a45ccc62c540fb89296199051fee142409138fd6e3eae409366bcd4720",
-      "dependencies": [
-        "jsr:@std/assert@^1.0.13",
-        "jsr:@std/data-structures",
-        "jsr:@std/fs@^1.0.17",
-        "jsr:@std/internal@^1.0.7",
-        "jsr:@std/path@^1.0.9"
-      ]
-    },
-    "@std/toml@1.0.6": {
-      "integrity": "da225502aecad66d8d778a635e9b78991997c2567ef8c6dbbd595c0cfce14c51",
+    "@std/toml@1.0.5": {
+      "integrity": "08061156e9c5716443a144b6e40a8668738b8b424ad99ab0b6fdf1b6ea4da806",
       "dependencies": [
-        "jsr:@std/collections@^1.1.0"
+        "jsr:@std/collections"
       ]
     },
     "@std/uuid@1.0.7": {
@@ -316,9 +265,6 @@
     },
     "@std/yaml@1.0.6": {
       "integrity": "c9a5a914e1d51c46756cb10e356710035cfa905e713c90d3b711413fd3aead27"
-    },
-    "@zip-js/zip-js@2.7.61": {
-      "integrity": "fb84a27455a79ce893d80150b5a6d26c1bdb744a7319c2a084a967c31c221ffc"
     }
   },
   "npm": {
@@ -773,8 +719,8 @@
         "lodash.uniq"
       ]
     },
-    "caniuse-lite@1.0.30001718": {
-      "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw=="
+    "caniuse-lite@1.0.30001717": {
+      "integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw=="
     },
     "chokidar@3.6.0": {
       "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
@@ -950,8 +896,8 @@
     "eastasianwidth@0.2.0": {
       "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
     },
-    "electron-to-chromium@1.5.155": {
-      "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng=="
+    "electron-to-chromium@1.5.151": {
+      "integrity": "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA=="
     },
     "emoji-regex@8.0.0": {
       "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
@@ -1192,14 +1138,14 @@
     "lru-cache@10.4.3": {
       "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
     },
-    "marked-mangle@1.1.10_marked@15.0.11": {
+    "marked-mangle@1.1.10_marked@15.0.12": {
       "integrity": "sha512-TrpN67SMJJdzXXWIzOd/QmnpsC5o1B44PUYaG2bh1XEbqVjA0UCI2ijFuE5LWESwKeI2gCP5FqcUHRGQwFtDIA==",
       "dependencies": [
         "marked"
       ]
     },
-    "marked@15.0.11": {
-      "integrity": "sha512-1BEXAU2euRCG3xwgLVT1y0xbJEld1XOrmRJpUwRCcy7rxhSCwMrmEu9LXoPhHSCJG41V7YcQ2mjKRr5BA3ITIA==",
+    "marked@15.0.12": {
+      "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
       "bin": true
     },
     "mdn-data@2.0.28": {
@@ -1774,8 +1720,8 @@
         "strip-ansi@7.1.0"
       ]
     },
-    "yaml@2.8.0": {
-      "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
+    "yaml@2.7.1": {
+      "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
       "bin": true
     }
   },
@@ -1818,6 +1764,34 @@
     "https://deno.land/x/case@2.1.1/vendor/camelCaseRegexp.ts": "7d9ff02aad4ab6429eeab7c7353f7bcdd6cc5909a8bd3dda97918c8bbb7621ae",
     "https://deno.land/x/case@2.1.1/vendor/camelCaseUpperRegexp.ts": "292de54a698370f90adcdf95727993d09888b7f33d17f72f8e54ba75f7791787",
     "https://deno.land/x/case@2.1.1/vendor/nonWordRegexp.ts": "c1a052629a694144b48c66b0175a22a83f4d61cb40f4e45293fc5d6b123f927e",
+    "https://deno.land/x/imagescript@1.3.0/ImageScript.js": "cf90773c966031edd781ed176c598f7ed495e7694cd9b86c986d2d97f783cca0",
+    "https://deno.land/x/imagescript@1.3.0/mod.ts": "18a6cb83c55e690c873505f6fe867364c678afb64934fe7aef593a6b92f79995",
+    "https://deno.land/x/imagescript@1.3.0/png/src/crc.mjs": "5cf50de181d61dd00e66a240d811018ba5070afa8bba302f393604404604de84",
+    "https://deno.land/x/imagescript@1.3.0/png/src/mem.mjs": "4968d400dae069b4bf0ef4767c1802fd2cc7d15d90eda4cfadf5b4cd19b96c6d",
+    "https://deno.land/x/imagescript@1.3.0/png/src/png.mjs": "96ef0ceff1b5a6cd9304749e5f187b4ab238509fb5f9a8be8ee934240271ed8d",
+    "https://deno.land/x/imagescript@1.3.0/png/src/zlib.mjs": "9867dc3fab1d31b664f9344b0d7e977f493d9c912a76c760d012ed2b89f7061c",
+    "https://deno.land/x/imagescript@1.3.0/utils/buffer.js": "952cb1beb8827e50a493a5d1f29a4845e8c648789406d389dd51f51205ba02d8",
+    "https://deno.land/x/imagescript@1.3.0/utils/crc32.js": "573d6222b3605890714ebc374e687ec2aa3e9a949223ea199483e47ca4864f7d",
+    "https://deno.land/x/imagescript@1.3.0/utils/png.js": "fbed9117e0a70602645d70df9c103ff6e79c03e987bd5c1685dcb4200729b6de",
+    "https://deno.land/x/imagescript@1.3.0/utils/wasm/font.js": "9e75d842608c057045698d6a7cdf5ffd27241b5cdea0391c89a1917b31294524",
+    "https://deno.land/x/imagescript@1.3.0/utils/wasm/gif.js": "8b86f7b96486bb8ff50fbc7c7487f86cb5cef85e6acd71e1def78a1aa2f12e4f",
+    "https://deno.land/x/imagescript@1.3.0/utils/wasm/jpeg.js": "75295e2fcf96b4f7bb894b3844fdaa8140d63169d28b466b5d5be89d59a7b6e6",
+    "https://deno.land/x/imagescript@1.3.0/utils/wasm/png.js": "0659536a8dd8f892c8346e268b2754b4414fad0ec1e9794dfcde1ba1c804ee02",
+    "https://deno.land/x/imagescript@1.3.0/utils/wasm/svg.js": "f5c8a9d1977b51a7c07549ceb6bbbaca9497321a193f28b3dc229a42d91bcf14",
+    "https://deno.land/x/imagescript@1.3.0/utils/wasm/tiff.js": "c2d7bdaef094df25aae1752e75167f485e89275d76a1379e39d8949580b7af4f",
+    "https://deno.land/x/imagescript@1.3.0/utils/wasm/zlib.js": "749875f83abffe24d3b977475a0cbd5f9b52bee1fbdbef61ec183cbfc17805f6",
+    "https://deno.land/x/imagescript@1.3.0/v2/framebuffer.mjs": "add44ff184636659714b3c6d4b896f628545451abffbc30b5bcc2e8d9a73d012",
+    "https://deno.land/x/imagescript@1.3.0/v2/ops/blur.mjs": "80716f1ffab8a2aeb54a036f583bf51a2b9dd37e005adc000add803df8e8a12f",
+    "https://deno.land/x/imagescript@1.3.0/v2/ops/color.mjs": "5e72cdcbf97dc939a2795223f01e3cb0544c0c56b03ea2aa026050df58348814",
+    "https://deno.land/x/imagescript@1.3.0/v2/ops/crop.mjs": "69431fa6f687fd9f0c31eff0ec27d7ac925275005e53a37f0c3fab4cc4d9a9ea",
+    "https://deno.land/x/imagescript@1.3.0/v2/ops/fill.mjs": "cf1b9488314753fbc9ebf03410ac74c2a34ea5a69fb6892cd6e8366cd1930d93",
+    "https://deno.land/x/imagescript@1.3.0/v2/ops/flip.mjs": "825a34a66567dcf15e76a719f1bf2f66fb106503cd69942292b1b0ae05c5718e",
+    "https://deno.land/x/imagescript@1.3.0/v2/ops/index.mjs": "423ba687119be2bba8cec72890577d3afa3621b6b8108912242fe937a183f2aa",
+    "https://deno.land/x/imagescript@1.3.0/v2/ops/iterator.mjs": "c2adf3d90ce00719a02c48c97634574176a3501ff026676259bd71aa8f5d69b9",
+    "https://deno.land/x/imagescript@1.3.0/v2/ops/overlay.mjs": "7e6e2c2ffd25006d52597ab8babc5f8f503d388a3fdf2fbc0eaea02799a020c9",
+    "https://deno.land/x/imagescript@1.3.0/v2/ops/resize.mjs": "814e78ebce8eaf8f1f918688db7b52a141405e06a36ed4b25d04413d69e7d17b",
+    "https://deno.land/x/imagescript@1.3.0/v2/ops/rotate.mjs": "a1b65616717bd2eed8db406affea3263b4674dada46b56441ef38167a187455d",
+    "https://deno.land/x/imagescript@1.3.0/v2/util/mem.mjs": "4968d400dae069b4bf0ef4767c1802fd2cc7d15d90eda4cfadf5b4cd19b96c6d",
     "https://esm.sh/@docsearch/js@3.5.2/es2020/js.mjs": "9b278cf3c0b26feded7d8efeac8e2b50f76bbafcf173a95002944bcc3482830a",
     "https://esm.sh/@docsearch/js@3.5.2?target=es2020": "4bad084f771a1923fe042ece62a9078f482f8642cb0b1acb890905e58586fee7",
     "https://raw.githubusercontent.com/denoland/ga4/main/mod.ts": "36f72ba1c90b5ebdb811427f367cd95fa6772d2de2fb45d6e57550501ee6d476",
@@ -1826,7 +1800,7 @@
   },
   "workspace": {
     "dependencies": [
-      "jsr:@astral/astral@~0.5.2",
+      "jsr:@astral/astral@~0.5.3",
       "jsr:@deno/doc@0.172",
       "jsr:@fresh/core@^2.0.0-alpha.29",
       "jsr:@fresh/plugin-tailwind@^0.0.1-alpha.7",
diff --git a/tests/test_utils.tsx b/tests/test_utils.tsx
index f98274a2aa7..f64136e3bfe 100644
--- a/tests/test_utils.tsx
+++ b/tests/test_utils.tsx
@@ -1,7 +1,7 @@
 import { App, getIslandRegistry, setBuildCache } from "../src/app.ts";
 import { launch, type Page } from "@astral/astral";
 import * as colors from "@std/fmt/colors";
-import { type Document, DOMParser, HTMLElement } from "linkedom";
+import { DOMParser, HTMLElement } from "linkedom";
 import { Builder } from "../src/dev/builder.ts";
 import { TextLineStream } from "@std/streams/text-line-stream";
 import * as path from "@std/path";
@@ -28,6 +28,16 @@ import { NodeProcess } from "./fixtures_islands/NodeProcess.tsx";
 import { FreshAttrs } from "./fixtures_islands/FreshAttrs.tsx";
 import { OptOutPartialLink } from "./fixtures_islands/OptOutPartialLink.tsx";
 
+const browser = await launch({
+  args: [
+    "--window-size=1280,720",
+    ...((Deno.env.get("CI") && Deno.build.os === "linux")
+      ? ["--no-sandbox"]
+      : []),
+  ],
+  headless: true,
+});
+
 export function getIsland(pathname: string) {
   return path.join(
     import.meta.dirname!,
@@ -79,45 +89,23 @@ export async function withBrowserApp(
   fn: (page: Page, address: string) => void | Promise<void>,
 ) {
   const aborter = new AbortController();
-  const server = Deno.serve({
+  await using server = Deno.serve({
     hostname: "localhost",
     port: 0,
     signal: aborter.signal,
     onListen: () => {}, // Don't spam terminal with "Listening on..."
   }, app.handler());
 
-  const browser = await launch({
-    args: [
-      "--window-size=1280,720",
-      ...((Deno.env.get("CI") && Deno.build.os === "linux")
-        ? ["--no-sandbox"]
-        : []),
-    ],
-    headless: true,
-  });
-
-  const page = await browser.newPage();
   try {
+    await using page = await browser.newPage();
     await fn(page, `http://localhost:${server.addr.port}`);
   } finally {
-    await page.close();
-    await browser.close();
     aborter.abort();
-    await server?.finished;
   }
 }
 
 export async function withBrowser(fn: (page: Page) => void | Promise<void>) {
-  const browser = await launch({
-    args: [
-      "--window-size=1280,7201",
-      ...((Deno.env.get("CI") && Deno.build.os === "linux")
-        ? ["--no-sandbox"]
-        : []),
-    ],
-    headless: true,
-  });
-  const page = await browser.newPage();
+  await using page = await browser.newPage();
   try {
     await fn(page);
   } catch (err) {
@@ -127,9 +115,6 @@ export async function withBrowser(fn: (page: Page) => void | Promise<void>) {
     // deno-lint-ignore no-console
     console.log(html);
     throw err;
-  } finally {
-    await page.close();
-    await browser.close();
   }
 }
 


Reviews:
user: login: lino-levan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T23:27:40Z
user: login: iuioiua
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T23:30:08Z
user: login: iuioiua
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T23:30:44Z
user: login: lino-levan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T23:32:08Z
user: login: iuioiua
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T00:26:58Z
user: login: marvinhagemeister
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T08:03:51Z
user: login: iuioiua
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T08:12:51Z
user: login: marvinhagemeister
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T09:25:10Z

Review comments:
diff_hunk: @@ -28,6 +28,20 @@ import { NodeProcess } from "./fixtures_islands/NodeProcess.tsx";
 import { FreshAttrs } from "./fixtures_islands/FreshAttrs.tsx";
 import { OptOutPartialLink } from "./fixtures_islands/OptOutPartialLink.tsx";
 
+const browser = await launch({
+  args: [
+    "--window-size=1280,7201",
+    ...((Deno.env.get("CI") && Deno.build.os === "linux")
+      ? ["--no-sandbox"]
path: tests/test_utils.tsx
user: login: lino-levan
body: https://github.com/lino-levan/astral?tab=readme-ov-file#no-usable-sandbox-with-user-namespace-cloning-enabled

I would recommend removing app armor instead of enabling --no-sandbox
created_at: 2025-05-07T23:27:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,6 +28,20 @@ import { NodeProcess } from "./fixtures_islands/NodeProcess.tsx";
 import { FreshAttrs } from "./fixtures_islands/FreshAttrs.tsx";
 import { OptOutPartialLink } from "./fixtures_islands/OptOutPartialLink.tsx";
 
+const browser = await launch({
+  args: [
+    "--window-size=1280,7201",
+    ...((Deno.env.get("CI") && Deno.build.os === "linux")
+      ? ["--no-sandbox"]
path: tests/test_utils.tsx
user: login: iuioiua
body: What is the advantage? Genuine question.
created_at: 2025-05-07T23:30:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,6 +28,20 @@ import { NodeProcess } from "./fixtures_islands/NodeProcess.tsx";
 import { FreshAttrs } from "./fixtures_islands/FreshAttrs.tsx";
 import { OptOutPartialLink } from "./fixtures_islands/OptOutPartialLink.tsx";
 
+const browser = await launch({
+  args: [
+    "--window-size=1280,7201",
+    ...((Deno.env.get("CI") && Deno.build.os === "linux")
+      ? ["--no-sandbox"]
path: tests/test_utils.tsx
user: login: iuioiua
body: Or do you think that's the issue here?
created_at: 2025-05-07T23:30:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,6 +28,20 @@ import { NodeProcess } from "./fixtures_islands/NodeProcess.tsx";
 import { FreshAttrs } from "./fixtures_islands/FreshAttrs.tsx";
 import { OptOutPartialLink } from "./fixtures_islands/OptOutPartialLink.tsx";
 
+const browser = await launch({
+  args: [
+    "--window-size=1280,7201",
+    ...((Deno.env.get("CI") && Deno.build.os === "linux")
+      ? ["--no-sandbox"]
path: tests/test_utils.tsx
user: login: lino-levan
body: The biggest reason is it cleans up the codebase. I've heard of really obscure bugs not being caught in CI because of `--no-sandbox`, but I've never seen one myself so take that with a grain of salt.
created_at: 2025-05-07T23:32:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,6 +28,23 @@ import { NodeProcess } from "./fixtures_islands/NodeProcess.tsx";
 import { FreshAttrs } from "./fixtures_islands/FreshAttrs.tsx";
 import { OptOutPartialLink } from "./fixtures_islands/OptOutPartialLink.tsx";
 
+/**
+ * If on Linux, disable `AppArmor` before running tests:
+ * ```sh
+ * echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
+ * ```
+ *
+ * @see {@link https://github.com/lino-levan/astral#no-usable-sandbox-with-user-namespace-cloning-enabled}
+ */
+const browser = await launch({
+  args: ["--window-size=1280,720"],
+  headless: true,
+});
+
+addEventListener("unload", async () => {
+  await browser.close();
+});
path: tests/test_utils.tsx
user: login: iuioiua
body: @lino-levan, when I use `await using`, tests hang.
created_at: 2025-05-08T00:24:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -48,6 +48,10 @@ jobs:
       - name: Type check project
         run: deno task check:types
 
+      - name: Disable AppArmor
path: .github/workflows/ci.yml
user: login: iuioiua
body: I think this is worth trying.
created_at: 2025-05-08T00:25:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,6 +28,23 @@ import { NodeProcess } from "./fixtures_islands/NodeProcess.tsx";
 import { FreshAttrs } from "./fixtures_islands/FreshAttrs.tsx";
 import { OptOutPartialLink } from "./fixtures_islands/OptOutPartialLink.tsx";
 
+/**
+ * If on Linux, disable `AppArmor` before running tests:
+ * ```sh
+ * echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
path: tests/test_utils.tsx
user: login: marvinhagemeister
body: I'm not sure if it's a good idea to tell contributors on Linux to disable `AppArmor` globally on their machines. Contributing to a JS framework shouldn't come with the requirements to disable central security features in the OS you're using.
created_at: 2025-05-12T08:03:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,6 +28,23 @@ import { NodeProcess } from "./fixtures_islands/NodeProcess.tsx";
 import { FreshAttrs } from "./fixtures_islands/FreshAttrs.tsx";
 import { OptOutPartialLink } from "./fixtures_islands/OptOutPartialLink.tsx";
 
+/**
+ * If on Linux, disable `AppArmor` before running tests:
+ * ```sh
+ * echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
path: tests/test_utils.tsx
user: login: iuioiua
body: Very good point. Silly for me to do. Reverted.
created_at: 2025-05-12T08:12:50Z
author_association: CONTRIBUTOR

PR comments:
user: login: iuioiua
created_at: 2025-05-12T01:16:18Z
body: PTAL @marvinhagemeister
author_association: CONTRIBUTOR
user: login: iuioiua
created_at: 2025-05-18T23:26:36Z
body: @marvinhagemeister, are you able to please take a look? It'd be great to have this bump in test performance.
author_association: CONTRIBUTOR