diff --git a/Examples/Testing/package.json b/Examples/Testing/package.json
new file mode 100644
index 00000000..2ce18c0a
--- /dev/null
+++ b/Examples/Testing/package.json
@@ -0,0 +1,5 @@
+{
+    "devDependencies": {
+        "playwright": "^1.52.0"
+    }
+}
diff --git a/Makefile b/Makefile
index e2aef5f8..e3f41cae 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,6 @@ SWIFT_SDK_ID ?= wasm32-unknown-wasi
 .PHONY: bootstrap
 bootstrap:
 	npm ci
-	npx playwright install
 
 .PHONY: unittest
 unittest:
diff --git a/Plugins/PackageToJS/Templates/package.json b/Plugins/PackageToJS/Templates/package.json
index 79562784..a41e6db2 100644
--- a/Plugins/PackageToJS/Templates/package.json
+++ b/Plugins/PackageToJS/Templates/package.json
@@ -10,7 +10,12 @@
     "dependencies": {
         "@bjorn3/browser_wasi_shim": "0.3.0"
     },
-    "devDependencies": {
+    "peerDependencies": {
         "playwright": "^1.51.0"
+    },
+    "peerDependenciesMeta": {
+        "playwright": {
+            "optional": true
+        }
     }
 }
diff --git a/Plugins/PackageToJS/Tests/ExampleTests.swift b/Plugins/PackageToJS/Tests/ExampleTests.swift
index 7c41cf3b..ab0d1d79 100644
--- a/Plugins/PackageToJS/Tests/ExampleTests.swift
+++ b/Plugins/PackageToJS/Tests/ExampleTests.swift
@@ -114,20 +114,17 @@ extension Trait where Self == ConditionTrait {
         }
     }
 
+    typealias RunProcess = (_ executableURL: URL, _ args: [String], _ env: [String: String]) throws -> Void
     typealias RunSwift = (_ args: [String], _ env: [String: String]) throws -> Void
 
-    func withPackage(at path: String, body: (URL, _ runSwift: RunSwift) throws -> Void) throws {
+    func withPackage(at path: String, body: (URL, _ runProcess: RunProcess, _ runSwift: RunSwift) throws -> Void) throws
+    {
         try withTemporaryDirectory { tempDir, retain in
             let destination = tempDir.appending(path: Self.repoPath.lastPathComponent)
             try Self.copyRepository(to: destination)
-            try body(destination.appending(path: path)) { args, env in
+            func runProcess(_ executableURL: URL, _ args: [String], _ env: [String: String]) throws {
                 let process = Process()
-                process.executableURL = URL(
-                    fileURLWithPath: "swift",
-                    relativeTo: URL(
-                        fileURLWithPath: try #require(Self.getSwiftPath())
-                    )
-                )
+                process.executableURL = executableURL
                 process.arguments = args
                 process.currentDirectoryURL = destination.appending(path: path)
                 process.environment = ProcessInfo.processInfo.environment.merging(env) { _, new in
@@ -157,13 +154,21 @@ extension Trait where Self == ConditionTrait {
                     """
                 )
             }
+            func runSwift(_ args: [String], _ env: [String: String]) throws {
+                let swiftExecutable = URL(
+                    fileURLWithPath: "swift",
+                    relativeTo: URL(fileURLWithPath: try #require(Self.getSwiftPath()))
+                )
+                try runProcess(swiftExecutable, args, env)
+            }
+            try body(destination.appending(path: path), runProcess, runSwift)
         }
     }
 
     @Test(.requireSwiftSDK)
     func basic() throws {
         let swiftSDKID = try #require(Self.getSwiftSDKID())
-        try withPackage(at: "Examples/Basic") { packageDir, runSwift in
+        try withPackage(at: "Examples/Basic") { packageDir, _, runSwift in
             try runSwift(["package", "--swift-sdk", swiftSDKID, "js"], [:])
             try runSwift(["package", "--swift-sdk", swiftSDKID, "js", "--debug-info-format", "dwarf"], [:])
             try runSwift(["package", "--swift-sdk", swiftSDKID, "js", "--debug-info-format", "name"], [:])
@@ -177,7 +182,10 @@ extension Trait where Self == ConditionTrait {
     @Test(.requireSwiftSDK)
     func testing() throws {
         let swiftSDKID = try #require(Self.getSwiftSDKID())
-        try withPackage(at: "Examples/Testing") { packageDir, runSwift in
+        try withPackage(at: "Examples/Testing") { packageDir, runProcess, runSwift in
+            try runProcess(which("npm"), ["install"], [:])
+            try runProcess(which("npx"), ["playwright", "install", "chromium-headless-shell"], [:])
+
             try runSwift(["package", "--swift-sdk", swiftSDKID, "js", "test"], [:])
             try withTemporaryDirectory(body: { tempDir, _ in
                 let scriptContent = """
@@ -208,7 +216,7 @@ extension Trait where Self == ConditionTrait {
     func testingWithCoverage() throws {
         let swiftSDKID = try #require(Self.getSwiftSDKID())
         let swiftPath = try #require(Self.getSwiftPath())
-        try withPackage(at: "Examples/Testing") { packageDir, runSwift in
+        try withPackage(at: "Examples/Testing") { packageDir, runProcess, runSwift in
             try runSwift(
                 ["package", "--swift-sdk", swiftSDKID, "js", "test", "--enable-code-coverage"],
                 [
@@ -216,19 +224,18 @@ extension Trait where Self == ConditionTrait {
                 ]
             )
             do {
-                let llvmCov = try which("llvm-cov")
-                let process = Process()
-                process.executableURL = llvmCov
                 let profdata = packageDir.appending(
                     path: ".build/plugins/PackageToJS/outputs/PackageTests/default.profdata"
                 )
-                let wasm = packageDir.appending(
-                    path: ".build/plugins/PackageToJS/outputs/PackageTests/TestingPackageTests.wasm"
+                let possibleWasmPaths = ["CounterPackageTests.xctest.wasm", "CounterPackageTests.wasm"].map {
+                    packageDir.appending(path: ".build/plugins/PackageToJS/outputs/PackageTests/\($0)")
+                }
+                let wasmPath = try #require(
+                    possibleWasmPaths.first(where: { FileManager.default.fileExists(atPath: $0.path) }),
+                    "No wasm file found"
                 )
-                process.arguments = ["report", "-instr-profile", profdata.path, wasm.path]
-                process.standardOutput = FileHandle.nullDevice
-                try process.run()
-                process.waitUntilExit()
+                let llvmCov = try which("llvm-cov")
+                try runProcess(llvmCov, ["report", "-instr-profile", profdata.path, wasmPath.path], [:])
             }
         }
     }
@@ -237,7 +244,7 @@ extension Trait where Self == ConditionTrait {
     @Test(.requireSwiftSDK(triple: "wasm32-unknown-wasip1-threads"))
     func multithreading() throws {
         let swiftSDKID = try #require(Self.getSwiftSDKID())
-        try withPackage(at: "Examples/Multithreading") { packageDir, runSwift in
+        try withPackage(at: "Examples/Multithreading") { packageDir, _, runSwift in
             try runSwift(["package", "--swift-sdk", swiftSDKID, "js"], [:])
         }
     }
@@ -245,7 +252,7 @@ extension Trait where Self == ConditionTrait {
     @Test(.requireSwiftSDK(triple: "wasm32-unknown-wasip1-threads"))
     func offscreenCanvas() throws {
         let swiftSDKID = try #require(Self.getSwiftSDKID())
-        try withPackage(at: "Examples/OffscrenCanvas") { packageDir, runSwift in
+        try withPackage(at: "Examples/OffscrenCanvas") { packageDir, _, runSwift in
             try runSwift(["package", "--swift-sdk", swiftSDKID, "js"], [:])
         }
     }
@@ -253,13 +260,13 @@ extension Trait where Self == ConditionTrait {
     @Test(.requireSwiftSDK(triple: "wasm32-unknown-wasip1-threads"))
     func actorOnWebWorker() throws {
         let swiftSDKID = try #require(Self.getSwiftSDKID())
-        try withPackage(at: "Examples/ActorOnWebWorker") { packageDir, runSwift in
+        try withPackage(at: "Examples/ActorOnWebWorker") { packageDir, _, runSwift in
             try runSwift(["package", "--swift-sdk", swiftSDKID, "js"], [:])
         }
     }
 
     @Test(.requireEmbeddedSwift) func embedded() throws {
-        try withPackage(at: "Examples/Embedded") { packageDir, runSwift in
+        try withPackage(at: "Examples/Embedded") { packageDir, _, runSwift in
             try runSwift(
                 ["package", "--triple", "wasm32-unknown-none-wasm", "js", "-c", "release"],
                 [
diff --git a/Sources/JavaScriptKit/ConvertibleToJSValue.swift b/Sources/JavaScriptKit/ConvertibleToJSValue.swift
index 805ee74d..afa63274 100644
--- a/Sources/JavaScriptKit/ConvertibleToJSValue.swift
+++ b/Sources/JavaScriptKit/ConvertibleToJSValue.swift
@@ -220,6 +220,10 @@ extension RawJSValue: ConvertibleToJSValue {
 
 extension JSValue {
     func withRawJSValue<T>(_ body: (RawJSValue) -> T) -> T {
+        body(convertToRawJSValue())
+    }
+
+    fileprivate func convertToRawJSValue() -> RawJSValue {
         let kind: JavaScriptValueKind
         let payload1: JavaScriptPayload1
         var payload2: JavaScriptPayload2 = 0
@@ -232,7 +236,9 @@ extension JSValue {
             payload1 = 0
             payload2 = numberValue
         case .string(let string):
-            return string.withRawJSValue(body)
+            kind = .string
+            payload1 = string.asInternalJSRef()
+            payload2 = 0
         case .object(let ref):
             kind = .object
             payload1 = JavaScriptPayload1(ref.id)
@@ -252,53 +258,28 @@ extension JSValue {
             kind = .bigInt
             payload1 = JavaScriptPayload1(bigIntRef.id)
         }
-        let rawValue = RawJSValue(kind: kind, payload1: payload1, payload2: payload2)
-        return body(rawValue)
+        return RawJSValue(kind: kind, payload1: payload1, payload2: payload2)
     }
 }
 
 extension Array where Element: ConvertibleToJSValue {
     func withRawJSValues<T>(_ body: ([RawJSValue]) -> T) -> T {
-        // fast path for empty array
-        guard self.count != 0 else { return body([]) }
-
-        func _withRawJSValues(
-            _ values: Self,
-            _ index: Int,
-            _ results: inout [RawJSValue],
-            _ body: ([RawJSValue]) -> T
-        ) -> T {
-            if index == values.count { return body(results) }
-            return values[index].jsValue.withRawJSValue { (rawValue) -> T in
-                results.append(rawValue)
-                return _withRawJSValues(values, index + 1, &results, body)
-            }
+        let jsValues = map { $0.jsValue }
+        // Ensure the jsValues live longer than the temporary raw JS values
+        return withExtendedLifetime(jsValues) {
+            body(jsValues.map { $0.convertToRawJSValue() })
         }
-        var _results = [RawJSValue]()
-        return _withRawJSValues(self, 0, &_results, body)
     }
 }
 
 #if !hasFeature(Embedded)
 extension Array where Element == ConvertibleToJSValue {
     func withRawJSValues<T>(_ body: ([RawJSValue]) -> T) -> T {
-        // fast path for empty array
-        guard self.count != 0 else { return body([]) }
-
-        func _withRawJSValues(
-            _ values: [ConvertibleToJSValue],
-            _ index: Int,
-            _ results: inout [RawJSValue],
-            _ body: ([RawJSValue]) -> T
-        ) -> T {
-            if index == values.count { return body(results) }
-            return values[index].jsValue.withRawJSValue { (rawValue) -> T in
-                results.append(rawValue)
-                return _withRawJSValues(values, index + 1, &results, body)
-            }
+        let jsValues = map { $0.jsValue }
+        // Ensure the jsValues live longer than the temporary raw JS values
+        return withExtendedLifetime(jsValues) {
+            body(jsValues.map { $0.convertToRawJSValue() })
         }
-        var _results = [RawJSValue]()
-        return _withRawJSValues(self, 0, &_results, body)
     }
 }
 #endif
diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSString.swift b/Sources/JavaScriptKit/FundamentalObjects/JSString.swift
index f084ffc8..4e6a0a08 100644
--- a/Sources/JavaScriptKit/FundamentalObjects/JSString.swift
+++ b/Sources/JavaScriptKit/FundamentalObjects/JSString.swift
@@ -97,13 +97,4 @@ extension JSString {
     func asInternalJSRef() -> JavaScriptObjectRef {
         guts.jsRef
     }
-
-    func withRawJSValue<T>(_ body: (RawJSValue) -> T) -> T {
-        let rawValue = RawJSValue(
-            kind: .string,
-            payload1: guts.jsRef,
-            payload2: 0
-        )
-        return body(rawValue)
-    }
 }
diff --git a/package-lock.json b/package-lock.json
index 55981f7b..e12af9c9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,7 @@
         "@bjorn3/browser_wasi_shim": "^0.4.1",
         "@rollup/plugin-typescript": "^12.1.2",
         "@types/node": "^22.13.14",
-        "playwright": "^1.51.0",
+        "playwright": "^1.52.0",
         "prettier": "3.5.3",
         "rollup": "^4.37.0",
         "rollup-plugin-dts": "^6.2.1",
@@ -507,13 +507,12 @@
       }
     },
     "node_modules/playwright": {
-      "version": "1.51.1",
-      "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.1.tgz",
-      "integrity": "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==",
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz",
+      "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==",
       "dev": true,
-      "license": "Apache-2.0",
       "dependencies": {
-        "playwright-core": "1.51.1"
+        "playwright-core": "1.52.0"
       },
       "bin": {
         "playwright": "cli.js"
@@ -526,11 +525,10 @@
       }
     },
     "node_modules/playwright-core": {
-      "version": "1.51.1",
-      "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.1.tgz",
-      "integrity": "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==",
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz",
+      "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==",
       "dev": true,
-      "license": "Apache-2.0",
       "bin": {
         "playwright-core": "cli.js"
       },
diff --git a/package.json b/package.json
index 867adb98..96443ad9 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,7 @@
     "@bjorn3/browser_wasi_shim": "^0.4.1",
     "@rollup/plugin-typescript": "^12.1.2",
     "@types/node": "^22.13.14",
-    "playwright": "^1.51.0",
+    "playwright": "^1.52.0",
     "prettier": "3.5.3",
     "rollup": "^4.37.0",
     "rollup-plugin-dts": "^6.2.1",