diff --git a/ci/test.mk b/ci/test.mk
index 6d0751360ee584d2ca3259bccb3703cb491e4e70..0fe0ce19abbe3d9d6fff2cd0d31ddf87e195ae1e 100644
--- a/ci/test.mk
+++ b/ci/test.mk
@@ -13,10 +13,10 @@ codecov: _gotest
 
 _gotest:
 	echo "--- gotest" && go test -parallel=32 -coverprofile=ci/out/coverage.prof -coverpkg=./... ./...
-	sed -i '/_stringer.go/d' ci/out/coverage.prof
-	sed -i '/wsjstest\/main.go/d' ci/out/coverage.prof
-	sed -i '/wsecho.go/d' ci/out/coverage.prof
-	sed -i '/assert.go/d' ci/out/coverage.prof
+	sed -i '/_stringer\.go/d' ci/out/coverage.prof
+	sed -i '/wsjstest\/main\.go/d' ci/out/coverage.prof
+	sed -i '/wsecho\.go/d' ci/out/coverage.prof
+	sed -i '/assert\.go/d' ci/out/coverage.prof
 
 gotest-wasm: wsjstest
 	echo "--- wsjstest" && ./ci/wasmtest.sh
diff --git a/ci/wasmtest.sh b/ci/wasmtest.sh
index 21335ce7c1904d6a150e92b078da0a8c1270d907..66d397a2c98665ab4f76a8bb3f4f0d1e555baf7e 100755
--- a/ci/wasmtest.sh
+++ b/ci/wasmtest.sh
@@ -2,8 +2,17 @@
 
 set -euo pipefail
 
-WS_ECHO_SERVER_URL="$(wsjstest)"
-trap 'pkill -KILL wsjstest' EXIT INT
+wsjstestOut="$(mktemp -d)/wsjstestOut"
+mkfifo "$wsjstestOut"
+timeout 15s wsjstest > "$wsjstestOut" &
+wsjstestPID="$!"
+
+WS_ECHO_SERVER_URL="$(head -n 1 "$wsjstestOut")"
 export WS_ECHO_SERVER_URL
 
 GOOS=js GOARCH=wasm go test -exec=wasmbrowsertest ./...
+
+if ! wait "$wsjstestPID" ; then
+  echo "wsjstest exited unsuccessfully"
+  exit 1
+fi
diff --git a/conn_common.go b/conn_common.go
index 5a11a79c904f890a1507a3ab85982e40a4c2f490..9f0b045a1c54f94057ae2fe932c14c7ccd6dfdf6 100644
--- a/conn_common.go
+++ b/conn_common.go
@@ -5,7 +5,6 @@ package websocket
 
 import (
 	"context"
-	"errors"
 	"fmt"
 	"io"
 	"math"
@@ -104,8 +103,8 @@ func (c *netConn) Read(p []byte) (int, error) {
 	if c.reader == nil {
 		typ, r, err := c.c.Reader(c.readContext)
 		if err != nil {
-			var ce CloseError
-			if errors.As(err, &ce) && (ce.Code == StatusNormalClosure) || (ce.Code == StatusGoingAway) {
+			switch CloseStatus(err) {
+			case StatusNormalClosure, StatusGoingAway:
 				c.eofed = true
 				return 0, io.EOF
 			}
diff --git a/conn_test.go b/conn_test.go
index f3f1a2b0a3380efe3a50c08effaeadc1f8f67367..8dcff944f8662ec187ef87565f84b980f951d615 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -589,11 +589,7 @@ func TestConn(t *testing.T) {
 					return err
 				}
 				_, _, err = c.Read(ctx)
-				var cerr websocket.CloseError
-				if !errors.As(err, &cerr) || cerr.Code != websocket.StatusProtocolError {
-					return fmt.Errorf("expected close error with StatusProtocolError: %+v", err)
-				}
-				return nil
+				return assertCloseStatus(err, websocket.StatusProtocolError)
 			},
 			client: func(ctx context.Context, c *websocket.Conn) error {
 				_, _, err := c.Read(ctx)
diff --git a/internal/wsjstest/main.go b/internal/wsjstest/main.go
index 5251fb84edec0e7823a005fba24972ec0a5908e2..b8b1cba25b6eb797f02545ca00f53e3dfa5fb3be 100644
--- a/internal/wsjstest/main.go
+++ b/internal/wsjstest/main.go
@@ -3,63 +3,20 @@
 package main
 
 import (
-	"errors"
 	"fmt"
 	"log"
-	"net"
 	"net/http"
+	"net/http/httptest"
 	"os"
-	"os/exec"
+	"runtime"
+	"strings"
 
 	"nhooyr.io/websocket"
 	"nhooyr.io/websocket/internal/wsecho"
 )
 
-func fork() net.Listener {
-	if os.Getenv("FORKED") != "" {
-		f := os.NewFile(3, "listener")
-		l, err := net.FileListener(f)
-		if err != nil {
-			log.Fatalf("failed to create listener from fd: %+v", err)
-		}
-		return l
-	}
-
-	l, err := net.Listen("tcp", "localhost:0")
-	if err != nil {
-		log.Fatalf("failed to listen: %+v", err)
-	}
-	f, err := l.(*net.TCPListener).File()
-	if err != nil {
-		log.Fatalf("failed to get file from tcp listener: %+v", err)
-	}
-
-	cmd := exec.Command(os.Args[0])
-	cmd.Stderr = os.Stderr
-	cmd.Env = append(os.Environ(),
-		fmt.Sprintf("FORKED=true"),
-	)
-	cmd.ExtraFiles = append(cmd.ExtraFiles, f)
-	err = cmd.Start()
-	if err != nil {
-		log.Fatalf("failed to start command: %+v", err)
-	}
-
-	fmt.Printf("ws://%v\n", l.Addr().String())
-	os.Exit(0)
-
-	panic("unreachable")
-}
-
 func main() {
-	l := fork()
-
-	err := serve(l)
-	log.Fatalf("failed to serve: %+v", err)
-}
-
-func serve(l net.Listener) error {
-	return http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
 			Subprotocols:       []string{"echo"},
 			InsecureSkipVerify: true,
@@ -70,11 +27,14 @@ func serve(l net.Listener) error {
 		defer c.Close(websocket.StatusInternalError, "")
 
 		err = wsecho.Loop(r.Context(), c)
-
-		var ce websocket.CloseError
-		if !errors.As(err, &ce) || ce.Code != websocket.StatusNormalClosure {
-			log.Fatalf("unexpected loop error: %+v", err)
+		if websocket.CloseStatus(err) != websocket.StatusNormalClosure {
+			log.Fatalf("unexpected echo loop error: %+v", err)
 		}
+
+		os.Exit(0)
 	}))
 
+	wsURL := strings.Replace(s.URL, "http", "ws", 1)
+	fmt.Printf("%v\n", wsURL)
+	runtime.Goexit()
 }