Roll third_party/webgpu-cts/ d3e4c5300..ec19459e7 (3 commits)

Includes substantial tooling changes to move the CTS cache from a GCP
bucket to the CTS repo.

Regenerated:
 - expectations.txt
 - compat-expectations.txt
 - ts_sources.txt
 - test_list.txt
 - resource_files.txt
 - webtest .html files

https://chromium.googlesource.com/external/github.com/gpuweb/cts/+log/d3e4c5300efa..ec19459e753f
 - ec1945 Add case cache to repo
 - c47509 Use `findFailedPixels` in `copyTextureToTexture` (#3175)
 - e14e41 Updates for writeTimestamp removal (#3167)

Created with './tools/run cts roll'

Fixed: dawn:2193
Change-Id: I0a32306eb9f067cd1ad71182ecae25e72bc94522
Cq-Include-Trybots: luci.chromium.try:android-dawn-arm-rel,android-dawn-arm64-rel,dawn-try-mac-arm64-rel,dawn-try-win10-x86-rel,linux-dawn-rel,mac-dawn-rel,win-dawn-rel
Include-Ci-Only-Tests: true
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/160090
Reviewed-by: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/DEPS b/DEPS
index 1b6deb3..d0f378e 100644
--- a/DEPS
+++ b/DEPS
@@ -216,7 +216,7 @@
 
   # WebGPU CTS - not used directly by Dawn, only transitively by Chromium.
   'third_party/webgpu-cts': {
-    'url': '{chromium_git}/external/github.com/gpuweb/cts@d3e4c5300efaec139cc5728c3455508386fe4db2',
+    'url': '{chromium_git}/external/github.com/gpuweb/cts@ec19459e753f2fc52426063bad568eb140b8ffea',
     'condition': 'build_with_chromium',
   },
 
diff --git a/third_party/gn/webgpu-cts/BUILD.gn b/third_party/gn/webgpu-cts/BUILD.gn
index 41d7d5a..57c5766 100644
--- a/third_party/gn/webgpu-cts/BUILD.gn
+++ b/third_party/gn/webgpu-cts/BUILD.gn
@@ -35,7 +35,6 @@
   public_deps = [
     ":compile_src",
     ":copy_resources",
-    ":gen_cache",
     ":verify_gen_ts_dep_list",
   ]
   data = [ "test_list.txt" ]
@@ -89,19 +88,29 @@
   resource_file_inputs += [ "$file" ]
 }
 
-copy("copy_resources") {
-  sources = []
-  data = []
+action("copy_resources") {
+  src_dir = "../../webgpu-cts/src/resources/"
+  dst_dir = "$target_gen_dir/../../webgpu-cts/resources/"
+
+  inputs = []
+  outputs = [ "$target_out_dir/run_$target_name.stamp" ]
   foreach(resource_file, resource_file_inputs) {
-    sources += [ "../../webgpu-cts/src/resources/$resource_file" ]
-
-    # Copy into resources/, instead of src/resources/, because compile_src
-    # wipes src/ before running.
-    data += [ "$target_gen_dir/../../webgpu-cts/resources/$resource_file" ]
+    inputs += [ "$src_dir/$resource_file" ]
+    outputs += [ "$dst_dir/$resource_file" ]
   }
+  data = outputs
 
-  outputs =
-      [ "$target_gen_dir/../../webgpu-cts/resources/{{source_file_part}}" ]
+  script = "${dawn_root}/webgpu-cts/scripts/copy_files.py"
+  args = [
+    "--src_dir",
+    rebase_path(src_dir),
+    "--dst_dir",
+    rebase_path(dst_dir),
+    "--file_list",
+    rebase_path("resource_files.txt"),
+    "--stamp",
+    rebase_path(outputs[0], root_build_dir),
+  ]
 }
 
 action("verify_gen_ts_dep_list") {
@@ -118,23 +127,3 @@
     rebase_path(outputs[0], root_build_dir),
   ]
 }
-
-action("gen_cache") {
-  script = "${dawn_root}/webgpu-cts/scripts/gen_cache.py"
-
-  deps = [ ":compile_src" ]
-  _gen_cache_js =
-      "$target_gen_dir/../../webgpu-cts/src-node/common/tools/gen_cache.js"
-  inputs = get_target_outputs(":compile_src") + [ _gen_cache_js ]
-  args = [
-    rebase_path(_gen_cache_js, root_build_dir),
-    rebase_path("$target_gen_dir/../../webgpu-cts/cache", root_build_dir),
-  ]
-
-  _outputs = read_file("cache_list.txt", "list lines")
-  outputs = []
-  foreach(file, _outputs) {
-    outputs += [ "$target_gen_dir/../../webgpu-cts/cache/$file" ]
-  }
-  data = outputs
-}
diff --git a/third_party/gn/webgpu-cts/cache_list.txt b/third_party/gn/webgpu-cts/cache_list.txt
deleted file mode 100644
index ce3f483..0000000
--- a/third_party/gn/webgpu-cts/cache_list.txt
+++ /dev/null
@@ -1,104 +0,0 @@
-data/webgpu/shader/execution/case-cache/abs.bin
-data/webgpu/shader/execution/case-cache/acos.bin
-data/webgpu/shader/execution/case-cache/acosh.bin
-data/webgpu/shader/execution/case-cache/asin.bin
-data/webgpu/shader/execution/case-cache/asinh.bin
-data/webgpu/shader/execution/case-cache/atan.bin
-data/webgpu/shader/execution/case-cache/atan2.bin
-data/webgpu/shader/execution/case-cache/atanh.bin
-data/webgpu/shader/execution/case-cache/binary/af_addition.bin
-data/webgpu/shader/execution/case-cache/binary/af_division.bin
-data/webgpu/shader/execution/case-cache/binary/af_logical.bin
-data/webgpu/shader/execution/case-cache/binary/af_matrix_addition.bin
-data/webgpu/shader/execution/case-cache/binary/af_matrix_subtraction.bin
-data/webgpu/shader/execution/case-cache/binary/af_multiplication.bin
-data/webgpu/shader/execution/case-cache/binary/af_remainder.bin
-data/webgpu/shader/execution/case-cache/binary/af_subtraction.bin
-data/webgpu/shader/execution/case-cache/binary/f16_addition.bin
-data/webgpu/shader/execution/case-cache/binary/f16_division.bin
-data/webgpu/shader/execution/case-cache/binary/f16_logical.bin
-data/webgpu/shader/execution/case-cache/binary/f16_matrix_addition.bin
-data/webgpu/shader/execution/case-cache/binary/f16_matrix_matrix_multiplication.bin
-data/webgpu/shader/execution/case-cache/binary/f16_matrix_scalar_multiplication.bin
-data/webgpu/shader/execution/case-cache/binary/f16_matrix_subtraction.bin
-data/webgpu/shader/execution/case-cache/binary/f16_matrix_vector_multiplication.bin
-data/webgpu/shader/execution/case-cache/binary/f16_multiplication.bin
-data/webgpu/shader/execution/case-cache/binary/f16_remainder.bin
-data/webgpu/shader/execution/case-cache/binary/f16_subtraction.bin
-data/webgpu/shader/execution/case-cache/binary/f32_addition.bin
-data/webgpu/shader/execution/case-cache/binary/f32_division.bin
-data/webgpu/shader/execution/case-cache/binary/f32_logical.bin
-data/webgpu/shader/execution/case-cache/binary/f32_matrix_addition.bin
-data/webgpu/shader/execution/case-cache/binary/f32_matrix_matrix_multiplication.bin
-data/webgpu/shader/execution/case-cache/binary/f32_matrix_scalar_multiplication.bin
-data/webgpu/shader/execution/case-cache/binary/f32_matrix_subtraction.bin
-data/webgpu/shader/execution/case-cache/binary/f32_matrix_vector_multiplication.bin
-data/webgpu/shader/execution/case-cache/binary/f32_multiplication.bin
-data/webgpu/shader/execution/case-cache/binary/f32_remainder.bin
-data/webgpu/shader/execution/case-cache/binary/f32_subtraction.bin
-data/webgpu/shader/execution/case-cache/binary/i32_arithmetic.bin
-data/webgpu/shader/execution/case-cache/binary/i32_comparison.bin
-data/webgpu/shader/execution/case-cache/binary/u32_arithmetic.bin
-data/webgpu/shader/execution/case-cache/binary/u32_comparison.bin
-data/webgpu/shader/execution/case-cache/bitcast.bin
-data/webgpu/shader/execution/case-cache/ceil.bin
-data/webgpu/shader/execution/case-cache/clamp.bin
-data/webgpu/shader/execution/case-cache/cos.bin
-data/webgpu/shader/execution/case-cache/cosh.bin
-data/webgpu/shader/execution/case-cache/cross.bin
-data/webgpu/shader/execution/case-cache/degrees.bin
-data/webgpu/shader/execution/case-cache/determinant.bin
-data/webgpu/shader/execution/case-cache/distance.bin
-data/webgpu/shader/execution/case-cache/dot.bin
-data/webgpu/shader/execution/case-cache/exp.bin
-data/webgpu/shader/execution/case-cache/exp2.bin
-data/webgpu/shader/execution/case-cache/faceForward.bin
-data/webgpu/shader/execution/case-cache/floor.bin
-data/webgpu/shader/execution/case-cache/fma.bin
-data/webgpu/shader/execution/case-cache/fract.bin
-data/webgpu/shader/execution/case-cache/frexp.bin
-data/webgpu/shader/execution/case-cache/inverseSqrt.bin
-data/webgpu/shader/execution/case-cache/ldexp.bin
-data/webgpu/shader/execution/case-cache/length.bin
-data/webgpu/shader/execution/case-cache/log.bin
-data/webgpu/shader/execution/case-cache/log2.bin
-data/webgpu/shader/execution/case-cache/max.bin
-data/webgpu/shader/execution/case-cache/min.bin
-data/webgpu/shader/execution/case-cache/mix.bin
-data/webgpu/shader/execution/case-cache/modf.bin
-data/webgpu/shader/execution/case-cache/normalize.bin
-data/webgpu/shader/execution/case-cache/pack2x16float.bin
-data/webgpu/shader/execution/case-cache/pow.bin
-data/webgpu/shader/execution/case-cache/quantizeToF16.bin
-data/webgpu/shader/execution/case-cache/radians.bin
-data/webgpu/shader/execution/case-cache/reflect.bin
-data/webgpu/shader/execution/case-cache/refract.bin
-data/webgpu/shader/execution/case-cache/round.bin
-data/webgpu/shader/execution/case-cache/saturate.bin
-data/webgpu/shader/execution/case-cache/sign.bin
-data/webgpu/shader/execution/case-cache/sin.bin
-data/webgpu/shader/execution/case-cache/sinh.bin
-data/webgpu/shader/execution/case-cache/smoothstep.bin
-data/webgpu/shader/execution/case-cache/sqrt.bin
-data/webgpu/shader/execution/case-cache/step.bin
-data/webgpu/shader/execution/case-cache/tan.bin
-data/webgpu/shader/execution/case-cache/tanh.bin
-data/webgpu/shader/execution/case-cache/transpose.bin
-data/webgpu/shader/execution/case-cache/trunc.bin
-data/webgpu/shader/execution/case-cache/unary/af_arithmetic.bin
-data/webgpu/shader/execution/case-cache/unary/af_assignment.bin
-data/webgpu/shader/execution/case-cache/unary/bool_conversion.bin
-data/webgpu/shader/execution/case-cache/unary/f16_arithmetic.bin
-data/webgpu/shader/execution/case-cache/unary/f16_conversion.bin
-data/webgpu/shader/execution/case-cache/unary/f32_arithmetic.bin
-data/webgpu/shader/execution/case-cache/unary/f32_conversion.bin
-data/webgpu/shader/execution/case-cache/unary/i32_arithmetic.bin
-data/webgpu/shader/execution/case-cache/unary/i32_complement.bin
-data/webgpu/shader/execution/case-cache/unary/i32_conversion.bin
-data/webgpu/shader/execution/case-cache/unary/u32_complement.bin
-data/webgpu/shader/execution/case-cache/unary/u32_conversion.bin
-data/webgpu/shader/execution/case-cache/unpack2x16float.bin
-data/webgpu/shader/execution/case-cache/unpack2x16snorm.bin
-data/webgpu/shader/execution/case-cache/unpack2x16unorm.bin
-data/webgpu/shader/execution/case-cache/unpack4x8snorm.bin
-data/webgpu/shader/execution/case-cache/unpack4x8unorm.bin
diff --git a/third_party/gn/webgpu-cts/resource_files.txt b/third_party/gn/webgpu-cts/resource_files.txt
index 4bdbb01..56a3d8b 100644
--- a/third_party/gn/webgpu-cts/resource_files.txt
+++ b/third_party/gn/webgpu-cts/resource_files.txt
@@ -1,4 +1,109 @@
 README.md
+cache/hashes.json
+cache/webgpu/shader/execution/abs.bin
+cache/webgpu/shader/execution/acos.bin
+cache/webgpu/shader/execution/acosh.bin
+cache/webgpu/shader/execution/asin.bin
+cache/webgpu/shader/execution/asinh.bin
+cache/webgpu/shader/execution/atan.bin
+cache/webgpu/shader/execution/atan2.bin
+cache/webgpu/shader/execution/atanh.bin
+cache/webgpu/shader/execution/binary/af_addition.bin
+cache/webgpu/shader/execution/binary/af_division.bin
+cache/webgpu/shader/execution/binary/af_logical.bin
+cache/webgpu/shader/execution/binary/af_matrix_addition.bin
+cache/webgpu/shader/execution/binary/af_matrix_subtraction.bin
+cache/webgpu/shader/execution/binary/af_multiplication.bin
+cache/webgpu/shader/execution/binary/af_remainder.bin
+cache/webgpu/shader/execution/binary/af_subtraction.bin
+cache/webgpu/shader/execution/binary/f16_addition.bin
+cache/webgpu/shader/execution/binary/f16_division.bin
+cache/webgpu/shader/execution/binary/f16_logical.bin
+cache/webgpu/shader/execution/binary/f16_matrix_addition.bin
+cache/webgpu/shader/execution/binary/f16_matrix_matrix_multiplication.bin
+cache/webgpu/shader/execution/binary/f16_matrix_scalar_multiplication.bin
+cache/webgpu/shader/execution/binary/f16_matrix_subtraction.bin
+cache/webgpu/shader/execution/binary/f16_matrix_vector_multiplication.bin
+cache/webgpu/shader/execution/binary/f16_multiplication.bin
+cache/webgpu/shader/execution/binary/f16_remainder.bin
+cache/webgpu/shader/execution/binary/f16_subtraction.bin
+cache/webgpu/shader/execution/binary/f32_addition.bin
+cache/webgpu/shader/execution/binary/f32_division.bin
+cache/webgpu/shader/execution/binary/f32_logical.bin
+cache/webgpu/shader/execution/binary/f32_matrix_addition.bin
+cache/webgpu/shader/execution/binary/f32_matrix_matrix_multiplication.bin
+cache/webgpu/shader/execution/binary/f32_matrix_scalar_multiplication.bin
+cache/webgpu/shader/execution/binary/f32_matrix_subtraction.bin
+cache/webgpu/shader/execution/binary/f32_matrix_vector_multiplication.bin
+cache/webgpu/shader/execution/binary/f32_multiplication.bin
+cache/webgpu/shader/execution/binary/f32_remainder.bin
+cache/webgpu/shader/execution/binary/f32_subtraction.bin
+cache/webgpu/shader/execution/binary/i32_arithmetic.bin
+cache/webgpu/shader/execution/binary/i32_comparison.bin
+cache/webgpu/shader/execution/binary/u32_arithmetic.bin
+cache/webgpu/shader/execution/binary/u32_comparison.bin
+cache/webgpu/shader/execution/bitcast.bin
+cache/webgpu/shader/execution/ceil.bin
+cache/webgpu/shader/execution/clamp.bin
+cache/webgpu/shader/execution/cos.bin
+cache/webgpu/shader/execution/cosh.bin
+cache/webgpu/shader/execution/cross.bin
+cache/webgpu/shader/execution/degrees.bin
+cache/webgpu/shader/execution/determinant.bin
+cache/webgpu/shader/execution/distance.bin
+cache/webgpu/shader/execution/dot.bin
+cache/webgpu/shader/execution/exp.bin
+cache/webgpu/shader/execution/exp2.bin
+cache/webgpu/shader/execution/faceForward.bin
+cache/webgpu/shader/execution/floor.bin
+cache/webgpu/shader/execution/fma.bin
+cache/webgpu/shader/execution/fract.bin
+cache/webgpu/shader/execution/frexp.bin
+cache/webgpu/shader/execution/inverseSqrt.bin
+cache/webgpu/shader/execution/ldexp.bin
+cache/webgpu/shader/execution/length.bin
+cache/webgpu/shader/execution/log.bin
+cache/webgpu/shader/execution/log2.bin
+cache/webgpu/shader/execution/max.bin
+cache/webgpu/shader/execution/min.bin
+cache/webgpu/shader/execution/mix.bin
+cache/webgpu/shader/execution/modf.bin
+cache/webgpu/shader/execution/normalize.bin
+cache/webgpu/shader/execution/pack2x16float.bin
+cache/webgpu/shader/execution/pow.bin
+cache/webgpu/shader/execution/quantizeToF16.bin
+cache/webgpu/shader/execution/radians.bin
+cache/webgpu/shader/execution/reflect.bin
+cache/webgpu/shader/execution/refract.bin
+cache/webgpu/shader/execution/round.bin
+cache/webgpu/shader/execution/saturate.bin
+cache/webgpu/shader/execution/sign.bin
+cache/webgpu/shader/execution/sin.bin
+cache/webgpu/shader/execution/sinh.bin
+cache/webgpu/shader/execution/smoothstep.bin
+cache/webgpu/shader/execution/sqrt.bin
+cache/webgpu/shader/execution/step.bin
+cache/webgpu/shader/execution/tan.bin
+cache/webgpu/shader/execution/tanh.bin
+cache/webgpu/shader/execution/transpose.bin
+cache/webgpu/shader/execution/trunc.bin
+cache/webgpu/shader/execution/unary/af_arithmetic.bin
+cache/webgpu/shader/execution/unary/af_assignment.bin
+cache/webgpu/shader/execution/unary/bool_conversion.bin
+cache/webgpu/shader/execution/unary/f16_arithmetic.bin
+cache/webgpu/shader/execution/unary/f16_conversion.bin
+cache/webgpu/shader/execution/unary/f32_arithmetic.bin
+cache/webgpu/shader/execution/unary/f32_conversion.bin
+cache/webgpu/shader/execution/unary/i32_arithmetic.bin
+cache/webgpu/shader/execution/unary/i32_complement.bin
+cache/webgpu/shader/execution/unary/i32_conversion.bin
+cache/webgpu/shader/execution/unary/u32_complement.bin
+cache/webgpu/shader/execution/unary/u32_conversion.bin
+cache/webgpu/shader/execution/unpack2x16float.bin
+cache/webgpu/shader/execution/unpack2x16snorm.bin
+cache/webgpu/shader/execution/unpack2x16unorm.bin
+cache/webgpu/shader/execution/unpack4x8snorm.bin
+cache/webgpu/shader/execution/unpack4x8unorm.bin
 four-colors-h264-bt601-rotate-180.mp4
 four-colors-h264-bt601-rotate-270.mp4
 four-colors-h264-bt601-rotate-90.mp4
diff --git a/third_party/gn/webgpu-cts/test_list.txt b/third_party/gn/webgpu-cts/test_list.txt
index a2ccc83..5a48cb1 100644
--- a/third_party/gn/webgpu-cts/test_list.txt
+++ b/third_party/gn/webgpu-cts/test_list.txt
@@ -7232,8 +7232,8 @@
 webgpu:api,validation,capability_checks,features,query_types:createQuerySet:type="occlusion";featureContainsTimestampQuery=true
 webgpu:api,validation,capability_checks,features,query_types:createQuerySet:type="timestamp";featureContainsTimestampQuery=false
 webgpu:api,validation,capability_checks,features,query_types:createQuerySet:type="timestamp";featureContainsTimestampQuery=true
-webgpu:api,validation,capability_checks,features,query_types:writeTimestamp:featureContainsTimestampQuery=false
-webgpu:api,validation,capability_checks,features,query_types:writeTimestamp:featureContainsTimestampQuery=true
+webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=false
+webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true
 webgpu:api,validation,capability_checks,features,texture_formats:texture_descriptor:format="depth32float-stencil8";enable_required_feature=true
 webgpu:api,validation,capability_checks,features,texture_formats:texture_descriptor:format="depth32float-stencil8";enable_required_feature=false
 webgpu:api,validation,capability_checks,features,texture_formats:texture_descriptor:format="bc1-rgba-unorm";enable_required_feature=true
@@ -22912,10 +22912,10 @@
 webgpu:api,validation,encoding,queries,general:occlusion_query,query_type:type="timestamp"
 webgpu:api,validation,encoding,queries,general:occlusion_query,invalid_query_set:
 webgpu:api,validation,encoding,queries,general:occlusion_query,query_index:
-webgpu:api,validation,encoding,queries,general:timestamp_query,query_type_and_index:type="occlusion"
-webgpu:api,validation,encoding,queries,general:timestamp_query,query_type_and_index:type="timestamp"
-webgpu:api,validation,encoding,queries,general:timestamp_query,invalid_query_set:
-webgpu:api,validation,encoding,queries,general:timestamp_query,device_mismatch:
+webgpu:api,validation,encoding,queries,general:writeTimestamp,query_type_and_index:type="occlusion"
+webgpu:api,validation,encoding,queries,general:writeTimestamp,query_type_and_index:type="timestamp"
+webgpu:api,validation,encoding,queries,general:writeTimestamp,invalid_query_set:
+webgpu:api,validation,encoding,queries,general:writeTimestamp,device_mismatch:
 webgpu:api,validation,encoding,queries,resolveQuerySet:queryset_and_destination_buffer_state:querySetState="valid";destinationState="valid"
 webgpu:api,validation,encoding,queries,resolveQuerySet:queryset_and_destination_buffer_state:querySetState="valid";destinationState="invalid"
 webgpu:api,validation,encoding,queries,resolveQuerySet:queryset_and_destination_buffer_state:querySetState="valid";destinationState="destroyed"
diff --git a/third_party/gn/webgpu-cts/ts_sources.txt b/third_party/gn/webgpu-cts/ts_sources.txt
index 5476a4a..6cce6e2 100644
--- a/third_party/gn/webgpu-cts/ts_sources.txt
+++ b/third_party/gn/webgpu-cts/ts_sources.txt
@@ -44,6 +44,8 @@
 src/common/tools/checklist.ts
 src/common/tools/crawl.ts
 src/common/tools/dev_server.ts
+src/common/util/crc32.ts
+src/common/util/parse_imports.ts
 src/common/tools/gen_cache.ts
 src/common/tools/gen_listings.ts
 src/common/tools/gen_wpt_cts_html.ts
@@ -117,6 +119,7 @@
 src/unittests/basic.spec.ts
 src/unittests/check_contents.spec.ts
 src/unittests/conversion.spec.ts
+src/unittests/crc32.spec.ts
 src/webgpu/shader/execution/expression/case_cache.ts
 src/webgpu/util/compare.ts
 src/webgpu/shader/execution/expression/expression.ts
@@ -129,6 +132,7 @@
 src/unittests/maths.spec.ts
 src/unittests/params_builder_and_utils.spec.ts
 src/unittests/params_builder_toplevel.spec.ts
+src/unittests/parse_imports.spec.ts
 src/unittests/preprocessor.spec.ts
 src/webgpu/util/prng.ts
 src/unittests/prng.spec.ts
diff --git a/third_party/webgpu-cts b/third_party/webgpu-cts
index d3e4c53..ec19459 160000
--- a/third_party/webgpu-cts
+++ b/third_party/webgpu-cts
@@ -1 +1 @@
-Subproject commit d3e4c5300efaec139cc5728c3455508386fe4db2
+Subproject commit ec19459e753f2fc52426063bad568eb140b8ffea
diff --git a/tools/src/auth/auth.go b/tools/src/auth/auth.go
index 1d91848..7f6bac7 100644
--- a/tools/src/auth/auth.go
+++ b/tools/src/auth/auth.go
@@ -35,16 +35,11 @@
 
 // DefaultAuthOptions returns the default authentication options for use by
 // command line arguments.
-// Set needsCloudScopes to true if the authentication requires cloud access.
-func DefaultAuthOptions(needsCloudScopes bool) auth.Options {
+func DefaultAuthOptions() auth.Options {
 	def := chromeinfra.DefaultAuthOptions()
 	def.SecretsDir = fileutils.ExpandHome("~/.config/dawn-cts")
 	def.Scopes = append(def.Scopes,
 		"https://www.googleapis.com/auth/gerritcodereview",
 		auth.OAuthScopeEmail)
-	if needsCloudScopes {
-		def.Scopes = append(def.Scopes,
-			"https://www.googleapis.com/auth/cloud-platform")
-	}
 	return def
 }
diff --git a/tools/src/cmd/add-gerrit-hashtags/main.go b/tools/src/cmd/add-gerrit-hashtags/main.go
index bc19920..bbd5dcd 100644
--- a/tools/src/cmd/add-gerrit-hashtags/main.go
+++ b/tools/src/cmd/add-gerrit-hashtags/main.go
@@ -78,7 +78,7 @@
 }
 
 func main() {
-	authFlags.Register(flag.CommandLine, auth.DefaultAuthOptions( /* needsCloudScopes */ false))
+	authFlags.Register(flag.CommandLine, auth.DefaultAuthOptions())
 
 	flag.Usage = func() {
 		out := flag.CommandLine.Output()
diff --git a/tools/src/cmd/auto-submit/main.go b/tools/src/cmd/auto-submit/main.go
index 4a3ea1e..18d18bf 100644
--- a/tools/src/cmd/auto-submit/main.go
+++ b/tools/src/cmd/auto-submit/main.go
@@ -75,7 +75,7 @@
 }
 
 func main() {
-	authFlags.Register(flag.CommandLine, auth.DefaultAuthOptions( /* needsCloudScopes */ false))
+	authFlags.Register(flag.CommandLine, auth.DefaultAuthOptions())
 
 	flag.Usage = func() {
 		out := flag.CommandLine.Output()
diff --git a/tools/src/cmd/cts/build_cache/build_cache.go b/tools/src/cmd/cts/build_cache/build_cache.go
deleted file mode 100644
index 2619b6a..0000000
--- a/tools/src/cmd/cts/build_cache/build_cache.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2023 The Dawn & Tint Authors
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-// 1. Redistributions of source code must retain the above copyright notice, this
-//    list of conditions and the following disclaimer.
-//
-// 2. Redistributions in binary form must reproduce the above copyright notice,
-//    this list of conditions and the following disclaimer in the documentation
-//    and/or other materials provided with the distribution.
-//
-// 3. Neither the name of the copyright holder nor the names of its
-//    contributors may be used to endorse or promote products derived from
-//    this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-package build_cache
-
-import (
-	"context"
-	"flag"
-	"fmt"
-	"os"
-	"os/exec"
-	"path/filepath"
-
-	"dawn.googlesource.com/dawn/tools/src/auth"
-	"dawn.googlesource.com/dawn/tools/src/cmd/cts/common"
-	"dawn.googlesource.com/dawn/tools/src/fileutils"
-	"go.chromium.org/luci/auth/client/authcli"
-)
-
-func init() {
-	common.Register(&cmd{})
-}
-
-type cmd struct {
-	flags struct {
-		nodePath     string
-		npmPath      string
-		ctsDir       string
-		cacheListOut string
-		authFlags    authcli.Flags
-	}
-}
-
-func (cmd) Name() string { return "build-cache" }
-
-func (cmd) Desc() string { return "builds the CTS test case cache and uploads it to GCP" }
-
-func (c *cmd) RegisterFlags(ctx context.Context, cfg common.Config) ([]string, error) {
-	dawnRoot := fileutils.DawnRoot()
-	ctsPath := filepath.Join(dawnRoot, "third_party", "webgpu-cts")
-	cacheListOut := filepath.Join(dawnRoot, "third_party", "gn", "webgpu-cts", "cache_list.txt")
-	npmPath, _ := exec.LookPath("npm")
-	flag.StringVar(&c.flags.nodePath, "node", fileutils.NodePath(), "path to node")
-	flag.StringVar(&c.flags.npmPath, "npm", npmPath, "path to npm")
-	flag.StringVar(&c.flags.ctsDir, "cts", ctsPath, "path to CTS")
-	flag.StringVar(&c.flags.cacheListOut, "out", cacheListOut, "path to cache_list.txt output file")
-	c.flags.authFlags.Register(flag.CommandLine, auth.DefaultAuthOptions( /* needsCloudScopes */ true))
-	return nil, nil
-}
-
-func (c *cmd) Run(ctx context.Context, cfg common.Config) error {
-	if err := common.InstallCTSDeps(ctx, c.flags.ctsDir, c.flags.npmPath); err != nil {
-		return err
-	}
-	list, err := common.BuildCache(ctx, c.flags.ctsDir, c.flags.nodePath, c.flags.npmPath, c.flags.authFlags)
-	if err != nil {
-		return fmt.Errorf("failed to build cache: %w", err)
-	}
-
-	if err := os.WriteFile(c.flags.cacheListOut, []byte(list), 0666); err != nil {
-		return fmt.Errorf("failed to write cache to '%v': %w", c.flags.cacheListOut, err)
-	}
-
-	return nil
-}
diff --git a/tools/src/cmd/cts/export/export.go b/tools/src/cmd/cts/export/export.go
index bdbb58a..7a1f181 100644
--- a/tools/src/cmd/cts/export/export.go
+++ b/tools/src/cmd/cts/export/export.go
@@ -71,7 +71,7 @@
 }
 
 func (c *cmd) RegisterFlags(ctx context.Context, cfg common.Config) ([]string, error) {
-	c.flags.auth.Register(flag.CommandLine, auth.DefaultAuthOptions( /* needsCloudScopes */ false))
+	c.flags.auth.Register(flag.CommandLine, auth.DefaultAuthOptions())
 	c.flags.results.RegisterFlags(cfg)
 	return nil, nil
 }
diff --git a/tools/src/cmd/cts/main.go b/tools/src/cmd/cts/main.go
index 54a5d26..015dd11 100644
--- a/tools/src/cmd/cts/main.go
+++ b/tools/src/cmd/cts/main.go
@@ -41,7 +41,6 @@
 	"dawn.googlesource.com/dawn/tools/src/subcmd"
 
 	// Register sub-commands
-	_ "dawn.googlesource.com/dawn/tools/src/cmd/cts/build_cache"
 	_ "dawn.googlesource.com/dawn/tools/src/cmd/cts/export"
 	_ "dawn.googlesource.com/dawn/tools/src/cmd/cts/format"
 	_ "dawn.googlesource.com/dawn/tools/src/cmd/cts/merge"
diff --git a/tools/src/cmd/cts/results/results.go b/tools/src/cmd/cts/results/results.go
index 9ea66aa..1d5d088 100644
--- a/tools/src/cmd/cts/results/results.go
+++ b/tools/src/cmd/cts/results/results.go
@@ -62,7 +62,7 @@
 func (c *cmd) RegisterFlags(ctx context.Context, cfg common.Config) ([]string, error) {
 	flag.StringVar(&c.flags.output, "o", "results.txt", "output file. '-' writes to stdout")
 	c.flags.source.RegisterFlags(cfg)
-	c.flags.auth.Register(flag.CommandLine, auth.DefaultAuthOptions( /* needsCloudScopes */ false))
+	c.flags.auth.Register(flag.CommandLine, auth.DefaultAuthOptions())
 	return nil, nil
 }
 
diff --git a/tools/src/cmd/cts/roll/roll.go b/tools/src/cmd/cts/roll/roll.go
index 14628bd..2f88573 100644
--- a/tools/src/cmd/cts/roll/roll.go
+++ b/tools/src/cmd/cts/roll/roll.go
@@ -57,6 +57,7 @@
 	"dawn.googlesource.com/dawn/tools/src/gerrit"
 	"dawn.googlesource.com/dawn/tools/src/git"
 	"dawn.googlesource.com/dawn/tools/src/gitiles"
+	"dawn.googlesource.com/dawn/tools/src/glob"
 	"dawn.googlesource.com/dawn/tools/src/resultsdb"
 	"go.chromium.org/luci/auth"
 	"go.chromium.org/luci/auth/client/authcli"
@@ -73,8 +74,6 @@
 	gitLinkPath          = "third_party/webgpu-cts"
 	tsSourcesRelPath     = "third_party/gn/webgpu-cts/ts_sources.txt"
 	testListRelPath      = "third_party/gn/webgpu-cts/test_list.txt"
-	cacheListRelPath     = "third_party/gn/webgpu-cts/cache_list.txt"
-	cacheTarGz           = "third_party/gn/webgpu-cts/cache.tar.gz"
 	resourceFilesRelPath = "third_party/gn/webgpu-cts/resource_files.txt"
 	webTestsPath         = "webgpu-cts/webtests"
 	refMain              = "refs/heads/main"
@@ -111,7 +110,7 @@
 func (c *cmd) RegisterFlags(ctx context.Context, cfg common.Config) ([]string, error) {
 	gitPath, _ := exec.LookPath("git")
 	npmPath, _ := exec.LookPath("npm")
-	c.flags.auth.Register(flag.CommandLine, commonAuth.DefaultAuthOptions( /* needsCloudScopes */ true))
+	c.flags.auth.Register(flag.CommandLine, commonAuth.DefaultAuthOptions())
 	flag.StringVar(&c.flags.gitPath, "git", gitPath, "path to git")
 	flag.StringVar(&c.flags.npmPath, "npm", npmPath, "path to npm")
 	flag.StringVar(&c.flags.nodePath, "node", fileutils.NodePath(), "path to node")
@@ -538,7 +537,6 @@
 	msg.WriteString(" - compat-expectations.txt\n")
 	msg.WriteString(" - ts_sources.txt\n")
 	msg.WriteString(" - test_list.txt\n")
-	msg.WriteString(" - cache_list.txt\n")
 	msg.WriteString(" - resource_files.txt\n")
 	msg.WriteString(" - webtest .html files\n")
 	msg.WriteString("\n\n")
@@ -695,7 +693,6 @@
 // file path to file content for the CTS roll's change. This includes:
 // * type-script source files
 // * CTS test list
-// * CTS cache list
 // * resource file list
 // * webtest file sources
 func (r *roller) generateFiles(ctx context.Context) (map[string]string, error) {
@@ -733,9 +730,6 @@
 		tsSourcesRelPath:     r.genTSDepList,
 		testListRelPath:      r.genTestList,
 		resourceFilesRelPath: r.genResourceFilesList,
-		cacheListRelPath: func(context.Context) (string, error) {
-			return common.BuildCache(ctx, r.ctsDir, r.flags.nodePath, r.flags.npmPath, r.flags.auth)
-		},
 	} {
 		relPath, generator := relPath, generator // Capture values, not iterators
 		wg.Add(1)
@@ -853,7 +847,7 @@
 // This list can be used to populate the resource_files.txt file.
 func (r *roller) genResourceFilesList(ctx context.Context) (string, error) {
 	dir := filepath.Join(r.ctsDir, "src", "resources")
-	files, err := filepath.Glob(filepath.Join(dir, "*"))
+	files, err := glob.Glob(filepath.Join(dir, "**"))
 	if err != nil {
 		return "", err
 	}
diff --git a/tools/src/cmd/cts/roll/roll_test.go b/tools/src/cmd/cts/roll/roll_test.go
index 552509b..867d338 100644
--- a/tools/src/cmd/cts/roll/roll_test.go
+++ b/tools/src/cmd/cts/roll/roll_test.go
@@ -76,7 +76,6 @@
  - compat-expectations.txt
  - ts_sources.txt
  - test_list.txt
- - cache_list.txt
  - resource_files.txt
  - webtest .html files
 
diff --git a/tools/src/cmd/cts/time/time.go b/tools/src/cmd/cts/time/time.go
index a75429a..3cb390e 100644
--- a/tools/src/cmd/cts/time/time.go
+++ b/tools/src/cmd/cts/time/time.go
@@ -69,7 +69,7 @@
 
 func (c *cmd) RegisterFlags(ctx context.Context, cfg common.Config) ([]string, error) {
 	c.flags.source.RegisterFlags(cfg)
-	c.flags.auth.Register(flag.CommandLine, auth.DefaultAuthOptions( /* needsCloudScopes */ false))
+	c.flags.auth.Register(flag.CommandLine, auth.DefaultAuthOptions())
 	flag.IntVar(&c.flags.topN, "top", 0, "print the top N slowest tests")
 	flag.BoolVar(&c.flags.histogram, "histogram", false, "print a histogram of test timings")
 	flag.StringVar(&c.flags.query, "query", "", "test query to filter results")
diff --git a/tools/src/cmd/cts/update/update.go b/tools/src/cmd/cts/update/update.go
index e2fd1521c..ea3b4fe 100644
--- a/tools/src/cmd/cts/update/update.go
+++ b/tools/src/cmd/cts/update/update.go
@@ -77,7 +77,7 @@
 
 func (c *cmd) RegisterFlags(ctx context.Context, cfg common.Config) ([]string, error) {
 	c.flags.results.RegisterFlags(cfg)
-	c.flags.auth.Register(flag.CommandLine, auth.DefaultAuthOptions( /* needsCloudScopes */ false))
+	c.flags.auth.Register(flag.CommandLine, auth.DefaultAuthOptions())
 	flag.BoolVar(&c.flags.verbose, "verbose", false, "emit additional logging")
 	flag.Var(&c.flags.expectations, "expectations", "path to CTS expectations file(s) to update")
 	return nil, nil
diff --git a/tools/src/cmd/gerrit-stats/main.go b/tools/src/cmd/gerrit-stats/main.go
index a19ac8c..eee7e6a 100644
--- a/tools/src/cmd/gerrit-stats/main.go
+++ b/tools/src/cmd/gerrit-stats/main.go
@@ -73,7 +73,7 @@
 }
 
 func main() {
-	authFlags.Register(flag.CommandLine, auth.DefaultAuthOptions( /* needsCloudScopes */ false))
+	authFlags.Register(flag.CommandLine, auth.DefaultAuthOptions())
 
 	flag.Parse()
 	if err := run(); err != nil {
diff --git a/tools/src/cmd/run-cts/chrome/cmd.go b/tools/src/cmd/run-cts/chrome/cmd.go
index 51814d9..1637dce 100644
--- a/tools/src/cmd/run-cts/chrome/cmd.go
+++ b/tools/src/cmd/run-cts/chrome/cmd.go
@@ -41,6 +41,7 @@
 
 	"dawn.googlesource.com/dawn/tools/src/cmd/run-cts/common"
 	"dawn.googlesource.com/dawn/tools/src/fileutils"
+	"github.com/chromedp/cdproto/runtime"
 	"github.com/chromedp/chromedp"
 	"golang.org/x/net/websocket"
 )
@@ -91,7 +92,10 @@
 		return fmt.Errorf("only a single query can be provided")
 	}
 
-	if err := c.state.CTS.BuildIfRequired(c.flags.Verbose); err != nil {
+	if err := c.state.CTS.Node.BuildIfRequired(c.flags.Verbose); err != nil {
+		return err
+	}
+	if err := c.state.CTS.Standalone.BuildIfRequired(c.flags.Verbose); err != nil {
 		return err
 	}
 
@@ -180,7 +184,7 @@
 	type Response struct {
 		Type     string
 		Message  string
-		Log      string
+		Log      string `json:"log"`
 		Status   string
 		Duration float64 `json:"js_duration_ms"`
 	}
@@ -191,8 +195,8 @@
 	handler := http.NewServeMux()
 	handler.HandleFunc("/test_page.html", serveFile("webgpu-cts/test_page.html"))
 	handler.HandleFunc("/test_runner.js", serveFile("webgpu-cts/test_runner.js"))
-	handler.HandleFunc("/third_party/webgpu-cts/cache/data/",
-		serveDir("/third_party/webgpu-cts/cache/data/", "third_party/webgpu-cts/out/data/"))
+	handler.HandleFunc("/third_party/webgpu-cts/resources/",
+		serveDir("/third_party/webgpu-cts/resources/", "third_party/webgpu-cts/out/resources/"))
 	handler.HandleFunc("/third_party/webgpu-cts/src/",
 		serveDir("/third_party/webgpu-cts/src/", "third_party/webgpu-cts/out/"))
 	handler.HandleFunc("/", websocket.Handler(func(ws *websocket.Conn) {
@@ -248,18 +252,39 @@
 	allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), execOpts...)
 	defer cancel()
 
-	runCtx, cancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(log.Printf))
+	runCtx, cancel := chromedp.NewContext(allocCtx,
+		chromedp.WithLogf(log.Printf),
+		chromedp.WithErrorf(log.Printf))
 	defer cancel()
+
+	if c.flags.Verbose {
+		chromedp.ListenTarget(runCtx, func(ev interface{}) {
+			// log.Printf("%T", ev)
+			switch ev := ev.(type) {
+			case *runtime.EventConsoleAPICalled:
+				args := make([]string, len(ev.Args))
+				for i := range ev.Args {
+					args[i] = string(ev.Args[i].Value)
+				}
+				log.Println(ev.Type, strings.Join(args, " "))
+			case *runtime.EventExceptionThrown:
+				log.Println(ev.ExceptionDetails.Error())
+			}
+		})
+	}
 	if err := chromedp.Run(runCtx,
 		chromedp.Navigate(origin+"/test_page.html"),
-		chromedp.Evaluate(fmt.Sprintf("window.setupWebsocket(%v);", port), nil),
-	); err != nil {
+		chromedp.Evaluate(fmt.Sprintf("window.setupWebsocket(%v);", port),
+			nil)); err != nil {
 		return err
 	}
 
 nextTestCase:
 	for idx := range testCaseIndices {
 		res := common.Result{Index: idx, TestCase: testCases[idx]}
+		if c.flags.Verbose {
+			fmt.Println("Starting", res.TestCase)
+		}
 		requests <- Request{Query: string(res.TestCase)}
 
 		for {
@@ -301,6 +326,10 @@
 func serveFile(relPath string) func(http.ResponseWriter, *http.Request) {
 	dawnRoot := fileutils.DawnRoot()
 	return func(w http.ResponseWriter, r *http.Request) {
+		fullPath := filepath.Join(dawnRoot, relPath)
+		if !fileutils.IsFile(fullPath) {
+			log.Printf("'%v' file does not exist", fullPath)
+		}
 		http.ServeFile(w, r, filepath.Join(dawnRoot, relPath))
 	}
 }
@@ -308,7 +337,10 @@
 func serveDir(remote, local string) func(http.ResponseWriter, *http.Request) {
 	dawnRoot := fileutils.DawnRoot()
 	return func(w http.ResponseWriter, r *http.Request) {
-		path := filepath.Join(dawnRoot, local, strings.TrimPrefix(r.URL.Path, remote))
-		http.ServeFile(w, r, path)
+		fullPath := filepath.Join(dawnRoot, local, strings.TrimPrefix(r.URL.Path, remote))
+		if !fileutils.IsFile(fullPath) {
+			log.Printf("'%v' file does not exist", fullPath)
+		}
+		http.ServeFile(w, r, fullPath)
 	}
 }
diff --git a/tools/src/cmd/run-cts/common/builder.go b/tools/src/cmd/run-cts/common/builder.go
new file mode 100644
index 0000000..0d79926
--- /dev/null
+++ b/tools/src/cmd/run-cts/common/builder.go
@@ -0,0 +1,130 @@
+// Copyright 2023 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package common
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"time"
+
+	"dawn.googlesource.com/dawn/tools/src/fileutils"
+)
+
+type Builder struct {
+	Name string // 'standalone', 'node'
+	Out  string // Absolute path to the output directory
+	CTS  string // Absolute path to the CTS root directory
+	npx  string // Path to the npx executable
+}
+
+// BuildIfRequired calls Build() if the CTS sources have been modified since the last build.
+func (b *Builder) BuildIfRequired(verbose bool) error {
+	name := fmt.Sprintf("CTS-%v", b.Name)
+
+	// Scan the CTS source to determine the most recent change to the CTS source
+	mostRecentSourceChange, err := scanSourceTimestamps(filepath.Join(b.Out, "../src"), verbose)
+	if err != nil {
+		return fmt.Errorf("failed to scan source files for modified timestamps: %w", err)
+	}
+
+	type Cache struct {
+		BuildTimestamp map[string]time.Time // name -> most recent timestamp
+	}
+	cache := Cache{BuildTimestamp: map[string]time.Time{}}
+
+	cachePath := ""
+	if home, err := os.UserHomeDir(); err == nil {
+		cacheDir := filepath.Join(home, ".cache/webgpu")
+		cachePath = filepath.Join(cacheDir, "run-cts.json")
+		os.MkdirAll(cacheDir, 0777)
+	}
+
+	needsRebuild := true
+	if cachePath != "" { // consult the cache to see if we need to rebuild
+		if cacheFile, err := os.Open(cachePath); err == nil {
+			if err := json.NewDecoder(cacheFile).Decode(&cache); err == nil {
+				if fileutils.IsDir(b.Out) {
+					needsRebuild = mostRecentSourceChange.After(cache.BuildTimestamp[b.Name])
+				}
+			}
+			cacheFile.Close()
+		}
+	}
+
+	if verbose {
+		fmt.Println(name, "needs rebuild:", needsRebuild)
+	}
+
+	if needsRebuild {
+		if err := b.Build(verbose); err != nil {
+			return fmt.Errorf("failed to build %v: %w", name, err)
+		}
+	}
+
+	if cachePath != "" {
+		// Update the cache timestamp
+		if cacheFile, err := os.Create(cachePath); err == nil {
+			cache.BuildTimestamp[b.Name] = mostRecentSourceChange
+			json.NewEncoder(cacheFile).Encode(&cache)
+			cacheFile.Close()
+		}
+	}
+
+	return nil
+}
+
+// Build executes the necessary build commands to build the CTS, including
+// copying the cache files from gen to the out directory and compiling the
+// TypeScript files down to JavaScript.
+func (b *Builder) Build(verbose bool) error {
+	if verbose {
+		start := time.Now()
+		fmt.Printf("Building CTS %v...\n", b.Name)
+		defer func() {
+			fmt.Println("completed in", time.Since(start))
+		}()
+	}
+
+	if err := os.MkdirAll(b.Out, 0777); err != nil {
+		return err
+	}
+
+	for _, action := range []string{"run:generate-version", b.Name} {
+		cmd := exec.Command(b.npx, "grunt", action)
+		cmd.Dir = b.CTS
+		out, err := cmd.CombinedOutput()
+		if err != nil {
+			return fmt.Errorf("%w: %v", err, string(out))
+		}
+	}
+
+	return nil
+}
diff --git a/tools/src/cmd/run-cts/common/cts.go b/tools/src/cmd/run-cts/common/cts.go
index 8501e69..dbfd421 100644
--- a/tools/src/cmd/run-cts/common/cts.go
+++ b/tools/src/cmd/run-cts/common/cts.go
@@ -28,119 +28,44 @@
 package common
 
 import (
-	"encoding/json"
 	"fmt"
 	"os"
 	"os/exec"
 	"path/filepath"
 	"strings"
 	"time"
-
-	"dawn.googlesource.com/dawn/tools/src/fileutils"
 )
 
 type CTS struct {
-	path string                   // Path to the CTS directory
-	npx  string                   // Path to npx executable (optional)
-	node string                   // Path to node executable
-	eval func(main string) string // Returns JavaScript to run the given typescript tool
+	path       string // Path to the CTS directory
+	npx        string // Path to npx executable
+	node       string // Path to node executable
+	Standalone Builder
+	Node       Builder
 }
 
 func NewCTS(cts, npx, node string) CTS {
-	return CTS{path: cts, npx: npx, node: node, eval: evalScriptUsingJIT}
-}
-
-// Eval returns the JavaScript to run the given typescript tool
-func (c CTS) Eval(main string) string {
-	return c.eval(main)
-}
-
-// BuildIfRequired calls Build() if the CTS sources have been modified since the
-// last build.
-func (c CTS) BuildIfRequired(verbose bool) error {
-	if c.npx == "" {
-		fmt.Println("npx not found on PATH. Using runtime TypeScript transpilation (slow)")
-		c.eval = evalScriptUsingJIT
-		return nil
+	return CTS{
+		path: cts,
+		npx:  npx,
+		node: node,
+		Standalone: Builder{
+			Name: "standalone",
+			Out:  filepath.Join(cts, "out"),
+			CTS:  cts,
+			npx:  npx,
+		},
+		Node: Builder{
+			Name: "node",
+			Out:  filepath.Join(cts, "out-node"),
+			CTS:  cts,
+			npx:  npx,
+		},
 	}
-
-	// Scan the CTS source to determine the most recent change to the CTS source
-	mostRecentSourceChange, err := scanSourceTimestamps(filepath.Join(c.path, "src"), verbose)
-	if err != nil {
-		return fmt.Errorf("failed to scan source files for modified timestamps: %w", err)
-	}
-
-	type Cache struct {
-		BuildTimestamp time.Time
-	}
-
-	cachePath := ""
-	if home, err := os.UserHomeDir(); err == nil {
-		cacheDir := filepath.Join(home, ".cache/webgpu")
-		cachePath = filepath.Join(cacheDir, "run-cts.json")
-		os.MkdirAll(cacheDir, 0777)
-	}
-
-	needsRebuild := true
-	if cachePath != "" { // consult the cache to see if we need to rebuild
-		if cacheFile, err := os.Open(cachePath); err == nil {
-			cache := Cache{}
-			if err := json.NewDecoder(cacheFile).Decode(&cache); err == nil {
-				if fileutils.IsDir(filepath.Join(c.path, "out-node")) {
-					needsRebuild = mostRecentSourceChange.After(cache.BuildTimestamp)
-				}
-			}
-			cacheFile.Close()
-		}
-	}
-
-	if verbose {
-		fmt.Println("CTS needs rebuild:", needsRebuild)
-	}
-
-	if needsRebuild {
-		if err := c.Build(verbose); err != nil {
-			return fmt.Errorf("failed to build CTS: %w", err)
-		}
-	}
-
-	if cachePath != "" { // consult the cache to see if we need to rebuild
-		if cacheFile, err := os.Create(cachePath); err == nil {
-			c := Cache{BuildTimestamp: mostRecentSourceChange}
-			json.NewEncoder(cacheFile).Encode(&c)
-			cacheFile.Close()
-		}
-	}
-
-	return nil
-}
-
-// Build calls `npx grunt run:build-out-node` in the CTS directory to compile
-// the TypeScript files down to JavaScript. Doing this once ahead of time can be
-// much faster than dynamically transpiling when there are many tests to run.
-func (c CTS) Build(verbose bool) error {
-	if verbose {
-		start := time.Now()
-		fmt.Println("Building CTS...")
-		defer func() {
-			fmt.Println("completed in", time.Since(start))
-		}()
-	}
-
-	cmd := exec.Command(c.npx, "grunt", "run:build-out-node")
-	cmd.Dir = c.path
-	out, err := cmd.CombinedOutput()
-	if err != nil {
-		return fmt.Errorf("%w: %v", err, string(out))
-	}
-
-	// Can evaluate with the faster .js pre-built
-	c.eval = evalUsingCompiled
-	return nil
 }
 
 // QueryTestCases returns all the test cases that match query.
-func (n *CTS) QueryTestCases(verbose bool, query string) ([]TestCase, error) {
+func (c *CTS) QueryTestCases(verbose bool, query string) ([]TestCase, error) {
 	if verbose {
 		start := time.Now()
 		fmt.Println("Gathering test cases...")
@@ -150,7 +75,7 @@
 	}
 
 	args := append([]string{
-		"-e", n.Eval("cmdline"),
+		"-e", "require('./out-node/common/runtime/cmdline.js');",
 		"--", // Start of arguments
 		// src/common/runtime/helper/sys.ts expects 'node file.js <args>'
 		// and slices away the first two arguments. When running with '-e', args
@@ -159,11 +84,11 @@
 		"--list",
 	}, query)
 
-	cmd := exec.Command(n.node, args...)
-	cmd.Dir = n.path
+	cmd := exec.Command(c.node, args...)
+	cmd.Dir = c.path
 	out, err := cmd.CombinedOutput()
 	if err != nil {
-		return nil, fmt.Errorf("%w\n%v", err, string(out))
+		return nil, fmt.Errorf("%v %v\n%w\n%v", c.node, args, err, string(out))
 	}
 
 	lines := strings.Split(string(out), "\n")
@@ -176,14 +101,6 @@
 	return list, nil
 }
 
-func evalUsingCompiled(main string) string {
-	return fmt.Sprintf(`require('./out-node/common/runtime/%v.js');`, main)
-}
-
-func evalScriptUsingJIT(main string) string {
-	return fmt.Sprintf(`require('./src/common/tools/setup-ts-in-node.js');require('./src/common/runtime/%v.ts');`, main)
-}
-
 // scanSourceTimestamps scans all the .js and .ts files in all subdirectories of
 // dir, and returns the file with the most recent timestamp.
 func scanSourceTimestamps(dir string, verbose bool) (time.Time, error) {
diff --git a/tools/src/cmd/run-cts/common/flags.go b/tools/src/cmd/run-cts/common/flags.go
index 5008e43..cf80106 100644
--- a/tools/src/cmd/run-cts/common/flags.go
+++ b/tools/src/cmd/run-cts/common/flags.go
@@ -51,7 +51,7 @@
 	ExpectationsPath string
 	CTS              string // Path to the CTS directory
 	Node             string // Path to the Node executable
-	Npx              string // Path to the npx executable (optional)
+	Npx              string // Path to the npx executable
 }
 
 func (f *Flags) Register() {
@@ -63,7 +63,7 @@
 	flag.StringVar(&f.ExpectationsPath, "expect", "", "path to expectations file")
 	flag.StringVar(&f.CTS, "cts", defaultCtsPath(), "root directory of WebGPU CTS")
 	flag.StringVar(&f.Node, "node", fileutils.NodePath(), "path to node executable")
-	flag.StringVar(&f.Npx, "npx", defaultNpxPath(), "path to npx executable (optional)")
+	flag.StringVar(&f.Npx, "npx", defaultNpxPath(), "path to npx executable")
 }
 
 // Process processes the flags, returning a State.
diff --git a/tools/src/cmd/run-cts/node/cmd.go b/tools/src/cmd/run-cts/node/cmd.go
index ec04063..b810bbb 100644
--- a/tools/src/cmd/run-cts/node/cmd.go
+++ b/tools/src/cmd/run-cts/node/cmd.go
@@ -166,7 +166,7 @@
 		return err
 	}
 
-	if err := c.state.CTS.BuildIfRequired(c.flags.Verbose); err != nil {
+	if err := c.state.CTS.Node.BuildIfRequired(c.flags.Verbose); err != nil {
 		return err
 	}
 
diff --git a/tools/src/cmd/run-cts/node/cmdline.go b/tools/src/cmd/run-cts/node/cmdline.go
index 0b8ac8c..b0c46ce 100644
--- a/tools/src/cmd/run-cts/node/cmdline.go
+++ b/tools/src/cmd/run-cts/node/cmdline.go
@@ -82,7 +82,7 @@
 	defer cancel()
 
 	args := []string{
-		"-e", c.state.CTS.Eval("cmdline"), // Evaluate 'eval'.
+		"-e", "require('./out-node/common/runtime/cmdline.js');",
 		"--",
 		// src/common/runtime/helper/sys.ts expects 'node file.js <args>'
 		// and slices away the first two arguments. When running with '-e', args
diff --git a/tools/src/cmd/run-cts/node/server.go b/tools/src/cmd/run-cts/node/server.go
index 62fe179..f06ac04 100644
--- a/tools/src/cmd/run-cts/node/server.go
+++ b/tools/src/cmd/run-cts/node/server.go
@@ -97,7 +97,7 @@
 	stopServer := func() {}
 	startServer := func() error {
 		args := []string{
-			"-e", c.state.CTS.Eval("server"), // Evaluate 'eval'.
+			"-e", "require('./out-node/common/runtime/server.js');",
 			"--",
 			// src/common/runtime/helper/sys.ts expects 'node file.js <args>'
 			// and slices away the first two arguments. When running with '-e', args
@@ -105,7 +105,6 @@
 			"placeholder-arg",
 			// Actual arguments begin here
 			"--gpu-provider", filepath.Join(c.flags.bin, "cts.js"),
-			"--data", filepath.Join(c.flags.CTS, "out-node", "data"),
 		}
 		if c.flags.Colors {
 			args = append(args, "--colors")
diff --git a/tools/src/cmd/snippets/main.go b/tools/src/cmd/snippets/main.go
index 3799831..0d51050 100644
--- a/tools/src/cmd/snippets/main.go
+++ b/tools/src/cmd/snippets/main.go
@@ -70,7 +70,7 @@
 }
 
 func main() {
-	authFlags.Register(flag.CommandLine, auth.DefaultAuthOptions( /* needsCloudScopes */ false))
+	authFlags.Register(flag.CommandLine, auth.DefaultAuthOptions())
 
 	flag.Parse()
 	if err := run(); err != nil {
diff --git a/tools/src/glob/glob.go b/tools/src/glob/glob.go
index 2c695ff..0e05306 100644
--- a/tools/src/glob/glob.go
+++ b/tools/src/glob/glob.go
@@ -40,7 +40,7 @@
 	"dawn.googlesource.com/dawn/tools/src/match"
 )
 
-// Glob returns all the strings that match the given filepath glob
+// Glob returns all the paths that match the given filepath glob
 func Glob(str string) ([]string, error) {
 	abs, err := filepath.Abs(str)
 	if err != nil {
diff --git a/webgpu-cts/compat-expectations.txt b/webgpu-cts/compat-expectations.txt
index b89cdf0..c00f247 100644
--- a/webgpu-cts/compat-expectations.txt
+++ b/webgpu-cts/compat-expectations.txt
@@ -74,7 +74,7 @@
 # results: [ Failure RetryOnFailure Skip Slow ]
 # END TAG HEADER
 
-# Last rolled: 2023-11-15 05:08:54AM
+# Last rolled: 2023-11-16 10:20:22AM
 
 # snorm / stencil8 issues
 crbug.com/dawn/667 webgpu:api,operation,command_buffer,image_copy:offsets_and_sizes:initMethod="WriteTexture";checkMethod="PartialCopyT2B";format="r8snorm";dimension="1d" [ Failure ]
diff --git a/webgpu-cts/expectations.txt b/webgpu-cts/expectations.txt
index 708dc8d..8511d1d 100644
--- a/webgpu-cts/expectations.txt
+++ b/webgpu-cts/expectations.txt
@@ -76,7 +76,7 @@
 # results: [ Failure RetryOnFailure Skip Slow ]
 # END TAG HEADER
 
-# Last rolled: 2023-11-15 05:08:54AM
+# Last rolled: 2023-11-16 10:20:22AM
 
 ################################################################################
 # copyToTexture failures on Linux
@@ -786,6 +786,27 @@
 crbug.com/dawn/0000 [ dawn-no-backend-validation nvidia-0x2184 target-cpu-64 ubuntu webgpu-adapter-default ] webgpu:api,operation,rendering,depth_bias:depth_bias:quadAngle=0;bias=-8388608;biasSlopeScale=0;biasClamp=0.125 [ Failure ]
 crbug.com/dawn/0000 [ dawn-backend-validation nvidia-0x2184 target-cpu-64 ubuntu webgpu-adapter-default ] webgpu:api,operation,rendering,depth_bias:depth_bias:quadAngle=0;bias=8388608;biasSlopeScale=0;biasClamp=0 [ Failure ]
 crbug.com/dawn/0000 [ dawn-no-backend-validation nvidia-0x2184 target-cpu-64 ubuntu webgpu-adapter-default ] webgpu:api,operation,rendering,depth_bias:depth_bias:quadAngle=0;bias=8388608;biasSlopeScale=0;biasClamp=0 [ Failure ]
+crbug.com/dawn/0000 [ amd-0x67ef dawn-backend-validation target-cpu-64 ventura webgpu-adapter-default ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ amd-0x67ef dawn-no-backend-validation target-cpu-64 ventura webgpu-adapter-default ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ android-t arm dawn-backend-validation target-cpu-64 webgpu-adapter-default ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ android-t arm dawn-no-backend-validation target-cpu-64 webgpu-adapter-default ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ apple-angle-metal-renderer:-apple-m2 dawn-backend-validation target-cpu-64 ventura webgpu-adapter-default ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ apple-angle-metal-renderer:-apple-m2 dawn-no-backend-validation target-cpu-64 ventura webgpu-adapter-default ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ dawn-backend-validation intel-0x3e9b target-cpu-64 ventura webgpu-adapter-default ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ dawn-backend-validation intel-0x9bc5 target-cpu-64 ubuntu webgpu-adapter-default ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ dawn-backend-validation intel-0x9bc5 target-cpu-64 webgpu-adapter-default webgpu-dxc-enabled win10 ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ dawn-backend-validation nvidia-0x2184 target-cpu-64 ubuntu webgpu-adapter-default ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ dawn-backend-validation nvidia-0x2184 target-cpu-64 webgpu-adapter-default webgpu-dxc-disabled win10 ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ dawn-backend-validation nvidia-0x2184 target-cpu-64 webgpu-adapter-default webgpu-dxc-enabled win10 ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ dawn-no-backend-validation intel-0x3e9b target-cpu-64 ventura webgpu-adapter-default ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ dawn-no-backend-validation intel-0x9bc5 target-cpu-64 ubuntu webgpu-adapter-default ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ dawn-no-backend-validation intel-0x9bc5 target-cpu-32 webgpu-adapter-default webgpu-dxc-disabled win10 ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ dawn-no-backend-validation intel-0x9bc5 target-cpu-64 webgpu-adapter-default webgpu-dxc-disabled win10 ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ dawn-no-backend-validation intel-0x9bc5 target-cpu-64 webgpu-adapter-default webgpu-dxc-enabled win10 ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ dawn-no-backend-validation nvidia-0x2184 target-cpu-64 ubuntu webgpu-adapter-default ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ dawn-no-backend-validation nvidia-0x2184 target-cpu-32 webgpu-adapter-default webgpu-dxc-disabled win10 ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ dawn-no-backend-validation nvidia-0x2184 target-cpu-64 webgpu-adapter-default webgpu-dxc-disabled win10 ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
+crbug.com/dawn/0000 [ dawn-no-backend-validation nvidia-0x2184 target-cpu-64 webgpu-adapter-default webgpu-dxc-enabled win10 ] webgpu:api,validation,capability_checks,features,query_types:timestamp:featureContainsTimestampQuery=true [ Failure ]
 crbug.com/dawn/0000 [ android-t arm dawn-backend-validation target-cpu-64 webgpu-adapter-default ] webgpu:api,validation,capability_checks,limits,maxTextureDimension2D:getCurrentTexture,at_over:limitTest="atMaximum";testValueName="atLimit";canvasType="offscreen" [ Failure ]
 crbug.com/dawn/0000 [ android-t arm dawn-no-backend-validation target-cpu-64 webgpu-adapter-default ] webgpu:api,validation,capability_checks,limits,maxTextureDimension2D:getCurrentTexture,at_over:limitTest="atMaximum";testValueName="atLimit";canvasType="offscreen" [ Failure ]
 crbug.com/dawn/0000 [ android-t arm dawn-backend-validation target-cpu-64 webgpu-adapter-default ] webgpu:api,validation,capability_checks,limits,maxTextureDimension2D:getCurrentTexture,at_over:limitTest="atMaximum";testValueName="atLimit";canvasType="onscreen" [ Failure ]
@@ -796,6 +817,8 @@
 crbug.com/dawn/0000 [ android-r dawn-no-backend-validation qualcomm target-cpu-32 webgpu-adapter-default ] webgpu:api,validation,error_scope:simple:errorType="out-of-memory";errorFilter="out-of-memory" [ Failure ]
 crbug.com/dawn/0000 [ android-r dawn-backend-validation qualcomm target-cpu-32 webgpu-adapter-default ] webgpu:api,validation,error_scope:simple:errorType="out-of-memory";errorFilter="validation" [ Failure ]
 crbug.com/dawn/0000 [ android-r dawn-no-backend-validation qualcomm target-cpu-32 webgpu-adapter-default ] webgpu:api,validation,error_scope:simple:errorType="out-of-memory";errorFilter="validation" [ Failure ]
+crbug.com/dawn/0000 [ dawn-no-backend-validation nvidia-0x2184 target-cpu-64 webgpu-adapter-default webgpu-dxc-disabled win10 ] webgpu:api,validation,state,device_lost,destroy:createTexture,2d,uncompressed_format:format="rgba16sint";usageType="texture";usageCopy="dst";awaitLost=false [ Failure ]
+crbug.com/dawn/0000 [ dawn-no-backend-validation nvidia-0x2184 target-cpu-64 webgpu-adapter-default webgpu-dxc-disabled win10 ] webgpu:api,validation,state,device_lost,destroy:createTexture,2d,uncompressed_format:format="rgba16uint";usageType="texture";usageCopy="src";awaitLost=false [ Failure ]
 crbug.com/dawn/0000 [ amd-0x67ef dawn-backend-validation target-cpu-64 ventura webgpu-adapter-default ] webgpu:shader,execution,expression,call,builtin,clamp:f32:* [ Failure ]
 crbug.com/dawn/0000 [ amd-0x67ef dawn-no-backend-validation target-cpu-64 ventura webgpu-adapter-default ] webgpu:shader,execution,expression,call,builtin,clamp:f32:* [ Failure ]
 crbug.com/dawn/0000 [ android-t arm dawn-backend-validation target-cpu-64 webgpu-adapter-default ] webgpu:shader,execution,expression,call,builtin,determinant:f16:inputSource="storage_r";dim=4 [ Failure ]
@@ -868,9 +891,13 @@
 crbug.com/dawn/0000 [ dawn-no-backend-validation nvidia-0x2184 target-cpu-64 webgpu-adapter-default webgpu-dxc-enabled win10 ] webgpu:shader,execution,expression,call,builtin,trunc:f16:inputSource="const";vectorize=4 [ Failure ]
 crbug.com/dawn/0000 [ amd-0x67ef dawn-backend-validation target-cpu-64 ventura webgpu-adapter-default ] webgpu:shader,execution,expression,call,builtin,unpack2x16snorm:* [ Failure ]
 crbug.com/dawn/0000 [ amd-0x67ef dawn-no-backend-validation target-cpu-64 ventura webgpu-adapter-default ] webgpu:shader,execution,expression,call,builtin,unpack2x16snorm:* [ Failure ]
+crbug.com/dawn/0000 [ dawn-backend-validation intel-0x9bc5 target-cpu-64 webgpu-adapter-default webgpu-dxc-enabled win10 ] webgpu:shader,validation,expression,access,vector:vector:case="a";vector_decl="param";vector_width=3;element_type="f16" [ Failure ]
+crbug.com/dawn/0000 [ dawn-backend-validation intel-0x9bc5 ubuntu webgpu-adapter-default ] webgpu:web_platform,canvas,getCurrentTexture:multiple_frames:* [ Failure ]
+crbug.com/dawn/0000 [ dawn-backend-validation intel-0x9bc5 ubuntu webgpu-adapter-default ] webgpu:web_platform,canvas,getCurrentTexture:resize:* [ Failure ]
 crbug.com/dawn/0000 [ android-r qualcomm ] webgpu:web_platform,canvas,readbackFromWebGPUCanvas:drawTo2DCanvas:* [ Failure ]
 crbug.com/dawn/0000 [ android-t arm ] webgpu:web_platform,canvas,readbackFromWebGPUCanvas:drawTo2DCanvas:* [ Failure ]
 crbug.com/dawn/0000 [ ventura ] webgpu:web_platform,copyToTexture,canvas:copy_contents_from_bitmaprenderer_context_canvas:* [ Failure ]
+crbug.com/dawn/0000 [ dawn-backend-validation intel-0x9bc5 ubuntu webgpu-adapter-default ] webgpu:web_platform,copyToTexture,canvas:copy_contents_from_gpu_context_canvas:* [ Failure ]
 crbug.com/dawn/0000 [ amd-0x67ef dawn-backend-validation ventura webgpu-adapter-swiftshader ] webgpu:web_platform,external_texture,* [ Failure ]
 crbug.com/dawn/0000 [ amd-0x67ef dawn-no-backend-validation ventura webgpu-adapter-swiftshader ] webgpu:web_platform,external_texture,* [ Failure ]
 crbug.com/dawn/0000 [ apple-angle-metal-renderer:-apple-m2 dawn-backend-validation ventura webgpu-adapter-swiftshader ] webgpu:web_platform,external_texture,* [ Failure ]
diff --git a/webgpu-cts/scripts/copy_files.py b/webgpu-cts/scripts/copy_files.py
new file mode 100755
index 0000000..13c8cc3
--- /dev/null
+++ b/webgpu-cts/scripts/copy_files.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+#
+# Copyright 2023 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import argparse
+import os
+import shutil
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--src_dir',
+                        required=True,
+                        help='The source directory for the copy.')
+    parser.add_argument('--dst_dir',
+                        required=True,
+                        help='The destination directory for the copy.')
+    parser.add_argument(
+        '--file_list',
+        required=True,
+        help='The text filepath to a newline separated list of files to copy.')
+    parser.add_argument('--stamp', help='Stamp file to write after success.')
+    args = parser.parse_args()
+
+    with open(args.file_list, 'r') as f:
+        for file in f.read().splitlines():
+            src = os.path.join(args.src_dir, file)
+            dst = os.path.join(args.dst_dir, file)
+            try:
+                shutil.copy(src, dst)
+            except shutil.SameFileError:
+                pass
+
+    if args.stamp:
+        with open(args.stamp, 'w') as f:
+            f.write('')
diff --git a/webgpu-cts/scripts/gen_cache.py b/webgpu-cts/scripts/gen_cache.py
deleted file mode 100644
index 46cb3af..0000000
--- a/webgpu-cts/scripts/gen_cache.py
+++ /dev/null
@@ -1,104 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright 2022 The Dawn & Tint Authors
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice, this
-#    list of conditions and the following disclaimer.
-#
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-#    this list of conditions and the following disclaimer in the documentation
-#    and/or other materials provided with the distribution.
-#
-# 3. Neither the name of the copyright holder nor the names of its
-#    contributors may be used to endorse or promote products derived from
-#    this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-import argparse
-import tarfile
-import datetime
-import os
-import subprocess
-import sys
-import tempfile
-
-script_root = os.path.dirname(os.path.abspath(sys.argv[0]))
-dawn_root = os.path.abspath(os.path.join(script_root, "../.."))
-build_dir = os.path.join(dawn_root, 'build')
-if not os.path.isfile(os.path.join(build_dir, "find_depot_tools.py")):
-    # try chromium path
-    build_dir = os.path.abspath(os.path.join(dawn_root, "../../build"))
-if not os.path.isfile(os.path.join(build_dir, "find_depot_tools.py")):
-    raise SystemExit('could not find build directory')
-sys.path.insert(0, build_dir)
-import find_depot_tools
-
-bucket = 'dawn-webgpu-cts-cache'
-
-
-def cts_hash():
-    deps_path = os.path.join(script_root, '../../third_party/webgpu-cts')
-    hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=deps_path)
-    return hash.decode('UTF-8').strip('\n')
-
-
-def download_from_bucket(name, dst):
-    gsutil = os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'gsutil.py')
-    try:
-        cmd = subprocess.run(
-            ['python3', gsutil, 'cp', 'gs://{}/{}'.format(bucket, name), dst],
-            cwd=script_root,
-            check=True,
-            stdout=subprocess.PIPE,
-            stderr=subprocess.STDOUT)
-    except subprocess.CalledProcessError as ex:
-        output = ex.stdout.decode('utf-8')
-        print('gsutil.py cp failed: {}'.format(output), file=sys.stderr)
-        raise
-
-
-def gen_cache(out_dir):
-    # Obtain the current hash of the CTS repo
-    hash = cts_hash()
-
-    # Download the cache.tar.gz compressed data from the GCP bucket
-    tmpDir = tempfile.TemporaryDirectory()
-    cacheTarPath = os.path.join(tmpDir.name, 'cache.tar.gz')
-    download_from_bucket(hash + "/data", cacheTarPath)
-
-    # Extract the cache.tar.gz into out_dir
-    tar = tarfile.open(cacheTarPath)
-    tar.extractall(out_dir)
-
-    # Update timestamps
-    now = datetime.datetime.now().timestamp()
-    for name in tar.getnames():
-        path = os.path.join(out_dir, name)
-        os.utime(path, (now, now))
-    tar.close()
-
-
-# Extract the cache for CTS runs.
-if __name__ == '__main__':
-    parser = argparse.ArgumentParser()
-
-    # TODO(bclayton): Unused. Remove
-    parser.add_argument('js_script', help='Path to gen_cache.js')
-
-    parser.add_argument('out_dir', help='Output directory for the cache')
-    args = parser.parse_args()
-
-    gen_cache(args.out_dir)
diff --git a/webgpu-cts/scripts/gen_ts_dep_lists.py b/webgpu-cts/scripts/gen_ts_dep_lists.py
index 2a47b42..4b34b4e 100755
--- a/webgpu-cts/scripts/gen_ts_dep_lists.py
+++ b/webgpu-cts/scripts/gen_ts_dep_lists.py
@@ -28,6 +28,7 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 import argparse
+import glob
 import os
 import sys
 
@@ -57,7 +58,12 @@
 
 
 def get_resource_files():
-    files = os.listdir(os.path.join(webgpu_cts_root_dir, 'src', 'resources'))
+    dir = os.path.join(webgpu_cts_root_dir, 'src', 'resources')
+    all = glob.iglob(os.path.join(dir, '**'), recursive=True)
+    files = [
+        os.path.relpath(f, dir).replace(os.sep, '/') for f in all
+        if os.path.isfile(f)
+    ]
     files.sort()
     return files
 
diff --git a/webgpu-cts/test_runner.js b/webgpu-cts/test_runner.js
index 4195c4c..bfa9763 100644
--- a/webgpu-cts/test_runner.js
+++ b/webgpu-cts/test_runner.js
@@ -27,6 +27,7 @@
 
 import { globalTestConfig } from '../third_party/webgpu-cts/src/common/framework/test_config.js';
 import { dataCache } from '../third_party/webgpu-cts/src/common/framework/data_cache.js';
+import { getResourcePath } from '../third_party/webgpu-cts/src/common/framework/resources.js';
 import { DefaultTestFileLoader } from '../third_party/webgpu-cts/src/common/internal/file_loader.js';
 import { prettyPrintLog } from '../third_party/webgpu-cts/src/common/internal/logging/log_message.js';
 import { Logger } from '../third_party/webgpu-cts/src/common/internal/logging/logger.js';
@@ -119,13 +120,13 @@
 
 dataCache.setStore({
   load: async (path) => {
-    if (path.endsWith('.json')) {
-      // TODO(bclayton): Remove this once https://github.com/gpuweb/cts/pull/3094 lands and rolls.
-      return await (await fetch(`/third_party/webgpu-cts/cache/data/${path}`)).text();
-    } else {
-      const response = await fetch(`/third_party/webgpu-cts/cache/data/${path}`);
-      return new Uint8Array(await response.arrayBuffer());
+    const fullPath = getResourcePath(`cache/${path}`);
+    const response = await fetch(fullPath);
+    if (!response.ok) {
+      sendMessageTestLogString(`failed to load cache file: ${fullPath}`)
+      return Promise.reject(response.statusText);
     }
+    return new Uint8Array(await response.arrayBuffer());
   }
 });
 
@@ -278,6 +279,13 @@
     });
 }
 
+function sendMessageTestLogString(msg) {
+  socket.send(JSON.stringify({
+    'type': 'TEST_LOG',
+    'log': msg + "\n"
+  }));
+}
+
 function sendMessageTestFinished() {
   socket.send('{"type":"TEST_FINISHED"}');
 }