Repository: bokeh/bokeh

Issue:
Hard pass/fail test of some kind of basic glyph drawing
The examples reports are useful but are no substitute for automation. The project needs test(s) for basic glyph drawing that will actually result in a hard failure if results are not pixel-matched to expected results. 

To start, perhaps this could be a single large plot with no grids, axes, toolbar, etc, that simply draws a few representatives of each glyph. This does not give fine resolution into problems immediately, but would be a "canary in the coalmine" that at least indicates when *some* problem with basic rendering has been introduced. 


Discussion:
I'm not sure if this goes in `0.12.5`, obviously it is overdue and the sooner, the better. 

ping @mattpap 
>  (...) some kind of basic glyph drawing

In the context of the recent regression, basic drawing actually worked and the failure was related to a limited set of functionality and thus limited subset of examples. Given this, the proposed solution wouldn't help here and we would have to still refer to unreliable examples' tests, as they currently are. I think that given our limited resources, we should fix existing infrastructure which we already should know well, instead of invest time in something brand new.
What to do to fix examples' tests? First of all, as much as they are automated, their intrinsic property is that you can't decide whether a change is intentional or is a result of a regression or a bug in newly introduced code or example. Thus human intervention will be always necessary in case of image differences. However, currently there is no possibility of a test run with no image differences and there are plenty of differences during each, because notebook and server tests don't work at all, MPL compat produces randomly different images even if there are no random numbers involved, there are some examples using unseeded random generators (jitter) and timeouts are used to determine when bokehjs finished rendering, which leads to examples randomly not rendering at all or rendering partially or fully. We need to fix those and then we will know if there are differences or not. If there are, then one will have to inspect differing images and decide if there's an issue or not. It used to work like this a long time ago and for a brief window of time. Additionally, JS errors must be reported in the test long and don't allow to proceed to image diff (this is a big and unappreciated regression). 
> the failure was related to a limited set of functionality and thus limited subset of examples.

My interpretation of things is a bit different: the change made things *work* in a limited case, and caused it to *fail* everywhere else (i.e., all the existing common cases). This is exactly the situation I was proposing to try and automate detection. I'm not necessarily proposing anything new either, perhaps:
* a single new selenium test 
* a single new example test and way to mark a subset of example tests as "strict"

You are absolutely correct that we can't currently expect *all* examples to be runnable and pixel-comparable in a completely automatable fashion. And I am not sure we ever could fix up, e.g. whatever causes the seaborn examples to look slightly different. What I am saying is that there is *some* set of simple, focused, purpose-specific  things that could (and therefore should) be written and compared automatically. And furthermore, that a set of initial strict things could be added to testing "now". That is, we should not wait for something perfect tomorrow, instead of having something valuable today. 

Resolving patch:
diff --git a/bokehjs/make/tasks/generate_defaults.py b/bokehjs/make/tasks/generate_defaults.py
--- a/bokehjs/make/tasks/generate_defaults.py
+++ b/bokehjs/make/tasks/generate_defaults.py
@@ -15,9 +15,7 @@
 dest_dir = sys.argv[1]
 
 classes = [member for name, member in inspect.getmembers(models) if inspect.isclass(member)]
-
 model_class = next(klass for klass in classes if klass.__name__ == 'Model')
-widget_class = next(klass for klass in classes if klass.__name__ == 'Widget')
 
 # getclasstree returns a list which contains [ (class, parentClass), [(subClassOfClass, class), ...]]
 # where the subclass list is omitted if there are no subclasses.
@@ -69,14 +67,6 @@ def leaves(tree, underneath):
         defaults[name] = default
     all_json[vm_name] = defaults
 
-widgets_json = {}
-for leaf_widget in leaves(all_tree, widget_class):
-    klass = leaf_widget[0]
-    vm_name = klass.__view_model__
-    if vm_name not in widgets_json:
-        widgets_json[vm_name] = all_json[vm_name]
-        del all_json[vm_name]
-
 def output_defaults_module(filename, defaults):
     dest = os.path.join(dest_dir, ".generated_defaults", filename)
 
@@ -90,7 +80,6 @@ def output_defaults_module(filename, defaults):
     with io.open(dest, "w", encoding="utf-8") as f:
         f.write(output)
 
-    print("Wrote %s with %d model classes" % (filename, len(defaults)))
+    print("Wrote %s with %d models" % (filename, len(defaults)))
 
-output_defaults_module('models_defaults.json', all_json)
-output_defaults_module('widgets_defaults.json', widgets_json)
+output_defaults_module("defaults.json", all_json)


Test patch:
diff --git a/bokehjs/make/tasks/test.ts b/bokehjs/make/tasks/test.ts
--- a/bokehjs/make/tasks/test.ts
+++ b/bokehjs/make/tasks/test.ts
@@ -110,25 +110,6 @@ task("test:size", ["test:codebase:compile"], async () => {
   await mocha(["./build/test/codebase/size.js"])
 })
 
-function bundle(name: string): void {
-  const linker = new Linker({
-    entries: [join(paths.build_dir.test, name, "index.js")],
-    bases: [paths.build_dir.test, "./node_modules"],
-    cache: join(paths.build_dir.test, `${name}.json`),
-    target: "ES2020",
-    minify: false,
-    externals: [/^@bokehjs\//],
-    prelude: () => default_prelude({global: "Tests"}),
-    shims: ["fs", "module"],
-  })
-
-  if (!argv.rebuild) linker.load_cache()
-  const [bundle] = linker.link()
-  linker.store_cache()
-
-  bundle.assemble().write(join(paths.build_dir.test, `${name}.js`))
-}
-
 function sys_path(): string {
   const path = [process.env.PATH]
 
@@ -184,18 +165,21 @@ async function headless(port: number): Promise<ChildProcess> {
       reject(new BuildError("headless", `timeout starting ${executable}`))
     }, 30000)
     proc.on("error", reject)
+    let buffer = ""
     proc.stderr.on("data", (chunk) => {
-      const text = `${chunk}`
-
-      for (const line of text.split("\n")) {
-        if (line.match(/DevTools listening/) != null) {
-          clearTimeout(timer)
-          console.log(line)
-          resolve(proc)
-        } else if (line.match(/bind() failed: Address already in use/)) {
-          clearTimeout(timer)
-          reject(new BuildError("headless", `can't start headless browser on port ${port}`))
-        }
+      buffer += `${chunk}`
+
+      const result = buffer.match(/DevTools listening [^\n]*\n/)
+      if (result != null) {
+        proc.stderr.removeAllListeners()
+        clearTimeout(timer)
+        const [line] = result
+        console.log(line.trim())
+        resolve(proc)
+      } else if (buffer.match(/bind\(\)/)) {
+        proc.stderr.removeAllListeners()
+        clearTimeout(timer)
+        reject(new BuildError("headless", `can't start headless browser on port ${port}`))
       }
     })
   })
@@ -261,7 +245,7 @@ function devtools(devtools_port: number, server_port: number, name: string, base
     opt("k", argv.k),
     opt("grep", argv.grep),
     opt("baselines-root", baselines_root),
-    `--screenshot=${argv.screenshot ?? "skip"}`,
+    `--screenshot=${argv.screenshot ?? "test"}`,
   ]
 
   if (argv.debug) {
@@ -292,31 +276,19 @@ function devtools(devtools_port: number, server_port: number, name: string, base
 
 const start_headless = task("test:start:headless", async () => {
   let port = 9222
-  if (await is_available(port)) {
-    await retry(async () => {
-      await headless(port)
-    }, 5)
-  } else if (argv.reuse !== true) {
-    await retry(async () => {
-      port = await find_port(port)
-      await headless(port)
-    }, 5)
-  } else {
-    log(`Reusing chromium browser instance on port ${port}`)
-  }
+  await retry(async () => {
+    port = await find_port(port)
+    await headless(port)
+  }, 3)
   return success(port)
 })
 
 const start_server = task("test:start:server", async () => {
   let port = 5777
-  if (await is_available(port)) {
-    await server(port)
-  } else if (argv.reuse !== true) {
+  await retry(async () => {
     port = await find_port(port)
     await server(port)
-  } else {
-    log(`Reusing devtools server instance on port ${port}`)
-  }
+  }, 3)
   return success(port)
 })
 
@@ -324,31 +296,58 @@ const start = task2("test:start", [start_headless, start_server], async (devtool
   return success([devtools_port, server_port] as [number, number])
 })
 
-task("test:compile", ["defaults:generate"], async () => {
-  const success = compile_typescript("./test/tsconfig.json", {log})
+function compile(name: string) {
+  const success = compile_typescript(`./test/${name}/tsconfig.json`, {log})
 
   if (argv.emitError && !success)
     process.exit(1)
-})
+}
+
+function bundle(name: string): void {
+  const linker = new Linker({
+    entries: [join(paths.build_dir.test, name, "index.js")],
+    bases: [paths.build_dir.test, "./node_modules"],
+    cache: join(paths.build_dir.test, `${name}.json`),
+    target: "ES2020",
+    minify: false,
+    externals: [/^@bokehjs\//],
+    prelude: () => default_prelude({global: "Tests"}),
+    shims: ["fs", "module"],
+  })
 
-task("test:bundle", ["test:unit:bundle", "test:integration:bundle"])
+  if (!argv.rebuild) linker.load_cache()
+  const [bundle] = linker.link()
+  linker.store_cache()
 
-task("test:build", ["test:bundle"])
+  bundle.assemble().write(join(paths.build_dir.test, `${name}.js`))
+}
+
+task("test:compile:unit", async () => compile("unit"))
+const unit_bundle = task("test:unit:bundle", ["test:compile:unit"], async () => bundle("unit"))
 
-const unit_bundle = task("test:unit:bundle", ["test:compile"], async () => {
-  bundle("unit")
-})
 task2("test:unit", [start, unit_bundle], async ([devtools_port, server_port]) => {
   await devtools(devtools_port, server_port, "unit")
   return success(undefined)
 })
 
-const integration_bundle = task("test:integration:bundle", ["test:compile"], async () => {
-  bundle("integration")
-})
+task("test:compile:integration", async () => compile("integration"))
+const integration_bundle = task("test:integration:bundle", ["test:compile:integration"], async () => bundle("integration"))
+
 task2("test:integration", [start, integration_bundle], async ([devtools_port, server_port]) => {
   await devtools(devtools_port, server_port, "integration", "test/baselines")
   return success(undefined)
 })
 
-task("test", ["test:size", "test:unit", "test:integration"])
+task("test:defaults:compile", ["defaults:generate"], async () => compile("defaults"))
+const defaults_bundle = task("test:defaults:bundle", ["test:defaults:compile"], async () => bundle("defaults"))
+
+task2("test:defaults", [start, defaults_bundle], async ([devtools_port, server_port]) => {
+  await devtools(devtools_port, server_port, "defaults")
+  return success(undefined)
+})
+
+task("test:bundle", ["test:defaults:bundle", "test:unit:bundle", "test:integration:bundle"])
+task("test:build", ["test:bundle"])
+
+task("test:lib", ["test:unit", "test:integration"])
+task("test", ["test:size", "test:defaults", "test:lib"])
diff --git a/bokehjs/test/.gitignore b/bokehjs/test/.gitignore
--- a/bokehjs/test/.gitignore
+++ b/bokehjs/test/.gitignore
@@ -1 +1,3 @@
-/report.json
+/baselines/linux/report.json
+/baselines/macos/report.json
+/baselines/windows/report.json
diff --git a/bokehjs/test/baselines/Bug__in_issue_#9522__disallows_arrow_to_be_positioned_correctly_in_stacked_layouts b/bokehjs/test/baselines/Bug__in_issue_#9522__disallows_arrow_to_be_positioned_correctly_in_stacked_layouts
--- a/bokehjs/test/baselines/Bug__in_issue_#9522__disallows_arrow_to_be_positioned_correctly_in_stacked_layouts
+++ b/bokehjs/test/baselines/Bug__in_issue_#9522__disallows_arrow_to_be_positioned_correctly_in_stacked_layouts
@@ -1,7 +1,7 @@
 Row bbox=[0, 0, 400, 200]
   Plot bbox=[0, 0, 200, 200]
-    LinearAxis bbox=[22, 176, 173, 24]
-    LinearAxis bbox=[0, 5, 22, 171]
+    LinearAxis bbox=[21, 175, 174, 25]
+    LinearAxis bbox=[0, 5, 21, 170]
   Plot bbox=[200, 0, 200, 200]
-    LinearAxis bbox=[22, 176, 173, 24]
-    LinearAxis bbox=[0, 5, 22, 171]
+    LinearAxis bbox=[21, 175, 174, 25]
+    LinearAxis bbox=[0, 5, 21, 170]
diff --git a/bokehjs/test/baselines/Bug__in_issue_#9703__disallows_ImageURL_glyph_to_set_anchor_and_angle_at_the_same_time b/bokehjs/test/baselines/Bug__in_issue_#9703__disallows_ImageURL_glyph_to_set_anchor_and_angle_at_the_same_time
--- a/bokehjs/test/baselines/Bug__in_issue_#9703__disallows_ImageURL_glyph_to_set_anchor_and_angle_at_the_same_time
+++ b/bokehjs/test/baselines/Bug__in_issue_#9703__disallows_ImageURL_glyph_to_set_anchor_and_angle_at_the_same_time
@@ -1,3 +1,3 @@
 Plot bbox=[0, 0, 300, 300]
-  LinearAxis bbox=[24, 276, 271, 24]
-  LinearAxis bbox=[0, 5, 24, 271]
+  LinearAxis bbox=[25, 275, 270, 25]
+  LinearAxis bbox=[0, 5, 25, 270]
diff --git a/bokehjs/test/baselines/Bug__in_issue_#9724__makes_automatic_padding_in_data_ranges_inconsistent b/bokehjs/test/baselines/Bug__in_issue_#9724__makes_automatic_padding_in_data_ranges_inconsistent
--- a/bokehjs/test/baselines/Bug__in_issue_#9724__makes_automatic_padding_in_data_ranges_inconsistent
+++ b/bokehjs/test/baselines/Bug__in_issue_#9724__makes_automatic_padding_in_data_ranges_inconsistent
@@ -1,13 +1,13 @@
 Row bbox=[0, 0, 600, 150]
   Plot bbox=[0, 0, 150, 150]
-    LinearAxis bbox=[35, 126, 110, 24]
-    LinearAxis bbox=[0, 5, 35, 121]
+    LinearAxis bbox=[35, 125, 110, 25]
+    LinearAxis bbox=[0, 5, 35, 120]
   Plot bbox=[150, 0, 150, 150]
-    LinearAxis bbox=[24, 126, 121, 24]
-    LinearAxis bbox=[0, 5, 24, 121]
+    LinearAxis bbox=[25, 125, 120, 25]
+    LinearAxis bbox=[0, 5, 25, 120]
   Plot bbox=[300, 0, 150, 150]
-    LinearAxis bbox=[24, 126, 121, 24]
-    LinearAxis bbox=[0, 5, 24, 121]
+    LinearAxis bbox=[25, 125, 120, 25]
+    LinearAxis bbox=[0, 5, 25, 120]
   Plot bbox=[450, 0, 150, 150]
-    LinearAxis bbox=[24, 126, 121, 24]
-    LinearAxis bbox=[0, 5, 24, 121]
+    LinearAxis bbox=[25, 125, 120, 25]
+    LinearAxis bbox=[0, 5, 25, 120]
diff --git a/bokehjs/test/baselines/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_fill_color_==_null b/bokehjs/test/baselines/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_fill_color_==_null
--- a/bokehjs/test/baselines/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_fill_color_==_null
+++ b/bokehjs/test/baselines/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_fill_color_==_null
@@ -1,7 +1,7 @@
 Row bbox=[0, 0, 400, 200]
   Plot bbox=[0, 0, 200, 200]
-    LinearAxis bbox=[28, 176, 167, 24]
-    LinearAxis bbox=[0, 5, 28, 171]
+    LinearAxis bbox=[28, 175, 167, 25]
+    LinearAxis bbox=[0, 5, 28, 170]
   Plot bbox=[200, 0, 200, 200]
-    LinearAxis bbox=[28, 176, 167, 24]
-    LinearAxis bbox=[0, 5, 28, 171]
+    LinearAxis bbox=[28, 175, 167, 25]
+    LinearAxis bbox=[0, 5, 28, 170]
diff --git a/bokehjs/test/baselines/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_line_color_==_null b/bokehjs/test/baselines/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_line_color_==_null
--- a/bokehjs/test/baselines/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_line_color_==_null
+++ b/bokehjs/test/baselines/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_line_color_==_null
@@ -1,7 +1,7 @@
 Row bbox=[0, 0, 400, 200]
   Plot bbox=[0, 0, 200, 200]
-    LinearAxis bbox=[28, 176, 167, 24]
-    LinearAxis bbox=[0, 5, 28, 171]
+    LinearAxis bbox=[28, 175, 167, 25]
+    LinearAxis bbox=[0, 5, 28, 170]
   Plot bbox=[200, 0, 200, 200]
-    LinearAxis bbox=[28, 176, 167, 24]
-    LinearAxis bbox=[0, 5, 28, 171]
+    LinearAxis bbox=[28, 175, 167, 25]
+    LinearAxis bbox=[0, 5, 28, 170]
diff --git a/bokehjs/test/baselines/Bug__in_issue_#9879__disallows_to_change_FactorRange_to_a_lower_dimension_with_a_different_number_of_factors b/bokehjs/test/baselines/Bug__in_issue_#9879__disallows_to_change_FactorRange_to_a_lower_dimension_with_a_different_number_of_factors
--- a/bokehjs/test/baselines/Bug__in_issue_#9879__disallows_to_change_FactorRange_to_a_lower_dimension_with_a_different_number_of_factors
+++ b/bokehjs/test/baselines/Bug__in_issue_#9879__disallows_to_change_FactorRange_to_a_lower_dimension_with_a_different_number_of_factors
@@ -1,3 +1,3 @@
 Plot bbox=[0, 0, 200, 200]
-  CategoricalAxis bbox=[28, 176, 167, 24]
-  LinearAxis bbox=[0, 5, 28, 171]
+  CategoricalAxis bbox=[28, 175, 167, 25]
+  LinearAxis bbox=[0, 5, 28, 170]
diff --git a/bokehjs/test/baselines/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout b/bokehjs/test/baselines/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout
--- a/bokehjs/test/baselines/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout
+++ b/bokehjs/test/baselines/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout
@@ -1,11 +1,11 @@
-Row bbox=[0, 0, 508, 229]
-  Plot bbox=[0, 0, 254, 229]
-    LinearAxis bbox=[24, 205, 200, 24]
-    LinearAxis bbox=[0, 5, 24, 200]
-    Title bbox=[24, 5, 200, 0]
-    ToolbarPanel bbox=[224, 5, 30, 200]
-  Plot bbox=[254, 0, 254, 229]
-    LinearAxis bbox=[24, 205, 200, 24]
-    LinearAxis bbox=[0, 5, 24, 200]
-    Title bbox=[24, 5, 200, 0]
-    ToolbarPanel bbox=[224, 5, 30, 200]
+Row bbox=[0, 0, 510, 230]
+  Plot bbox=[0, 0, 255, 230]
+    LinearAxis bbox=[25, 205, 200, 25]
+    LinearAxis bbox=[0, 5, 25, 200]
+    Title bbox=[25, 5, 200, 0]
+    ToolbarPanel bbox=[225, 5, 30, 200]
+  Plot bbox=[255, 0, 255, 230]
+    LinearAxis bbox=[25, 205, 200, 25]
+    LinearAxis bbox=[0, 5, 25, 200]
+    Title bbox=[25, 5, 200, 0]
+    ToolbarPanel bbox=[225, 5, 30, 200]
diff --git a/bokehjs/test/baselines/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout_with_a_title b/bokehjs/test/baselines/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout_with_a_title
--- a/bokehjs/test/baselines/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout_with_a_title
+++ b/bokehjs/test/baselines/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout_with_a_title
@@ -1,11 +1,11 @@
-Row bbox=[0, 0, 508, 252]
-  Plot bbox=[0, 0, 254, 252]
-    LinearAxis bbox=[24, 228, 200, 24]
-    LinearAxis bbox=[0, 28, 24, 200]
-    Title bbox=[24, 0, 200, 28]
-    ToolbarPanel bbox=[224, 28, 30, 200]
-  Plot bbox=[254, 0, 254, 229]
-    LinearAxis bbox=[24, 205, 200, 24]
-    LinearAxis bbox=[0, 5, 24, 200]
-    Title bbox=[24, 5, 200, 0]
-    ToolbarPanel bbox=[224, 5, 30, 200]
+Row bbox=[0, 0, 510, 253]
+  Plot bbox=[0, 0, 255, 253]
+    LinearAxis bbox=[25, 228, 200, 25]
+    LinearAxis bbox=[0, 28, 25, 200]
+    Title bbox=[25, 0, 200, 28]
+    ToolbarPanel bbox=[225, 28, 30, 200]
+  Plot bbox=[255, 0, 255, 230]
+    LinearAxis bbox=[25, 205, 200, 25]
+    LinearAxis bbox=[0, 5, 25, 200]
+    Title bbox=[25, 5, 200, 0]
+    ToolbarPanel bbox=[225, 5, 30, 200]
diff --git a/bokehjs/test/baselines/Plot__should_allow_fixed_frame_300px_x_300px b/bokehjs/test/baselines/Plot__should_allow_fixed_frame_300px_x_300px
--- a/bokehjs/test/baselines/Plot__should_allow_fixed_frame_300px_x_300px
+++ b/bokehjs/test/baselines/Plot__should_allow_fixed_frame_300px_x_300px
@@ -1,5 +1,5 @@
-Plot bbox=[0, 0, 354, 329]
-  LinearAxis bbox=[24, 305, 300, 24]
-  LinearAxis bbox=[0, 5, 24, 300]
-  Title bbox=[24, 5, 300, 0]
-  ToolbarPanel bbox=[324, 5, 30, 300]
+Plot bbox=[0, 0, 355, 330]
+  LinearAxis bbox=[25, 305, 300, 25]
+  LinearAxis bbox=[0, 5, 25, 300]
+  Title bbox=[25, 5, 300, 0]
+  ToolbarPanel bbox=[325, 5, 30, 300]
diff --git a/bokehjs/test/baselines/Plot__should_allow_fixed_x_fixed_plot b/bokehjs/test/baselines/Plot__should_allow_fixed_x_fixed_plot
--- a/bokehjs/test/baselines/Plot__should_allow_fixed_x_fixed_plot
+++ b/bokehjs/test/baselines/Plot__should_allow_fixed_x_fixed_plot
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 200, 200]
-  LinearAxis bbox=[24, 176, 146, 24]
-  LinearAxis bbox=[0, 5, 24, 171]
-  Title bbox=[24, 5, 146, 0]
-  ToolbarPanel bbox=[170, 5, 30, 171]
+  LinearAxis bbox=[25, 175, 145, 25]
+  LinearAxis bbox=[0, 5, 25, 170]
+  Title bbox=[25, 5, 145, 0]
+  ToolbarPanel bbox=[170, 5, 30, 170]
diff --git a/bokehjs/test/baselines/Plot__should_allow_fixed_x_max_plot b/bokehjs/test/baselines/Plot__should_allow_fixed_x_max_plot
--- a/bokehjs/test/baselines/Plot__should_allow_fixed_x_max_plot
+++ b/bokehjs/test/baselines/Plot__should_allow_fixed_x_max_plot
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 200, 300]
-  LinearAxis bbox=[24, 276, 146, 24]
-  LinearAxis bbox=[0, 5, 24, 271]
-  Title bbox=[24, 5, 146, 0]
-  ToolbarPanel bbox=[170, 5, 30, 271]
+  LinearAxis bbox=[25, 275, 145, 25]
+  LinearAxis bbox=[0, 5, 25, 270]
+  Title bbox=[25, 5, 145, 0]
+  ToolbarPanel bbox=[170, 5, 30, 270]
diff --git a/bokehjs/test/baselines/Plot__should_allow_fixed_x_max_plot,_aspect_0.8 b/bokehjs/test/baselines/Plot__should_allow_fixed_x_max_plot,_aspect_0.8
--- a/bokehjs/test/baselines/Plot__should_allow_fixed_x_max_plot,_aspect_0.8
+++ b/bokehjs/test/baselines/Plot__should_allow_fixed_x_max_plot,_aspect_0.8
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 200, 250]
-  LinearAxis bbox=[24, 226, 146, 24]
-  LinearAxis bbox=[0, 5, 24, 221]
-  Title bbox=[24, 5, 146, 0]
-  ToolbarPanel bbox=[170, 5, 30, 221]
+  LinearAxis bbox=[25, 225, 145, 25]
+  LinearAxis bbox=[0, 5, 25, 220]
+  Title bbox=[25, 5, 145, 0]
+  ToolbarPanel bbox=[170, 5, 30, 220]
diff --git a/bokehjs/test/baselines/Plot__should_allow_fixed_x_max_plot,_aspect_1.0 b/bokehjs/test/baselines/Plot__should_allow_fixed_x_max_plot,_aspect_1.0
--- a/bokehjs/test/baselines/Plot__should_allow_fixed_x_max_plot,_aspect_1.0
+++ b/bokehjs/test/baselines/Plot__should_allow_fixed_x_max_plot,_aspect_1.0
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 200, 200]
-  LinearAxis bbox=[24, 176, 146, 24]
-  LinearAxis bbox=[0, 5, 24, 171]
-  Title bbox=[24, 5, 146, 0]
-  ToolbarPanel bbox=[170, 5, 30, 171]
+  LinearAxis bbox=[25, 175, 145, 25]
+  LinearAxis bbox=[0, 5, 25, 170]
+  Title bbox=[25, 5, 145, 0]
+  ToolbarPanel bbox=[170, 5, 30, 170]
diff --git a/bokehjs/test/baselines/Plot__should_allow_fixed_x_max_plot,_aspect_1.25 b/bokehjs/test/baselines/Plot__should_allow_fixed_x_max_plot,_aspect_1.25
--- a/bokehjs/test/baselines/Plot__should_allow_fixed_x_max_plot,_aspect_1.25
+++ b/bokehjs/test/baselines/Plot__should_allow_fixed_x_max_plot,_aspect_1.25
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 200, 160]
-  LinearAxis bbox=[24, 136, 146, 24]
-  LinearAxis bbox=[0, 5, 24, 131]
-  Title bbox=[24, 5, 146, 0]
-  ToolbarPanel bbox=[170, 5, 30, 131]
+  LinearAxis bbox=[25, 135, 145, 25]
+  LinearAxis bbox=[0, 5, 25, 130]
+  Title bbox=[25, 5, 145, 0]
+  ToolbarPanel bbox=[170, 5, 30, 130]
diff --git a/bokehjs/test/baselines/Plot__should_allow_max_x_fixed_plot b/bokehjs/test/baselines/Plot__should_allow_max_x_fixed_plot
--- a/bokehjs/test/baselines/Plot__should_allow_max_x_fixed_plot
+++ b/bokehjs/test/baselines/Plot__should_allow_max_x_fixed_plot
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 300, 200]
-  LinearAxis bbox=[24, 176, 246, 24]
-  LinearAxis bbox=[0, 5, 24, 171]
-  Title bbox=[24, 5, 246, 0]
-  ToolbarPanel bbox=[270, 5, 30, 171]
+  LinearAxis bbox=[25, 175, 245, 25]
+  LinearAxis bbox=[0, 5, 25, 170]
+  Title bbox=[25, 5, 245, 0]
+  ToolbarPanel bbox=[270, 5, 30, 170]
diff --git a/bokehjs/test/baselines/Plot__should_allow_max_x_fixed_plot,_aspect_0.8 b/bokehjs/test/baselines/Plot__should_allow_max_x_fixed_plot,_aspect_0.8
--- a/bokehjs/test/baselines/Plot__should_allow_max_x_fixed_plot,_aspect_0.8
+++ b/bokehjs/test/baselines/Plot__should_allow_max_x_fixed_plot,_aspect_0.8
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 160, 200]
-  LinearAxis bbox=[24, 176, 106, 24]
-  LinearAxis bbox=[0, 5, 24, 171]
-  Title bbox=[24, 5, 106, 0]
-  ToolbarPanel bbox=[130, 5, 30, 171]
+  LinearAxis bbox=[25, 175, 105, 25]
+  LinearAxis bbox=[0, 5, 25, 170]
+  Title bbox=[25, 5, 105, 0]
+  ToolbarPanel bbox=[130, 5, 30, 170]
diff --git a/bokehjs/test/baselines/Plot__should_allow_max_x_fixed_plot,_aspect_1.0 b/bokehjs/test/baselines/Plot__should_allow_max_x_fixed_plot,_aspect_1.0
--- a/bokehjs/test/baselines/Plot__should_allow_max_x_fixed_plot,_aspect_1.0
+++ b/bokehjs/test/baselines/Plot__should_allow_max_x_fixed_plot,_aspect_1.0
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 200, 200]
-  LinearAxis bbox=[24, 176, 146, 24]
-  LinearAxis bbox=[0, 5, 24, 171]
-  Title bbox=[24, 5, 146, 0]
-  ToolbarPanel bbox=[170, 5, 30, 171]
+  LinearAxis bbox=[25, 175, 145, 25]
+  LinearAxis bbox=[0, 5, 25, 170]
+  Title bbox=[25, 5, 145, 0]
+  ToolbarPanel bbox=[170, 5, 30, 170]
diff --git a/bokehjs/test/baselines/Plot__should_allow_max_x_fixed_plot,_aspect_1.25 b/bokehjs/test/baselines/Plot__should_allow_max_x_fixed_plot,_aspect_1.25
--- a/bokehjs/test/baselines/Plot__should_allow_max_x_fixed_plot,_aspect_1.25
+++ b/bokehjs/test/baselines/Plot__should_allow_max_x_fixed_plot,_aspect_1.25
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 250, 200]
-  LinearAxis bbox=[24, 176, 196, 24]
-  LinearAxis bbox=[0, 5, 24, 171]
-  Title bbox=[24, 5, 196, 0]
-  ToolbarPanel bbox=[220, 5, 30, 171]
+  LinearAxis bbox=[25, 175, 195, 25]
+  LinearAxis bbox=[0, 5, 25, 170]
+  Title bbox=[25, 5, 195, 0]
+  ToolbarPanel bbox=[220, 5, 30, 170]
diff --git a/bokehjs/test/baselines/Plot__should_allow_max_x_max_plot b/bokehjs/test/baselines/Plot__should_allow_max_x_max_plot
--- a/bokehjs/test/baselines/Plot__should_allow_max_x_max_plot
+++ b/bokehjs/test/baselines/Plot__should_allow_max_x_max_plot
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 300, 300]
-  LinearAxis bbox=[24, 276, 246, 24]
-  LinearAxis bbox=[0, 5, 24, 271]
-  Title bbox=[24, 5, 246, 0]
-  ToolbarPanel bbox=[270, 5, 30, 271]
+  LinearAxis bbox=[25, 275, 245, 25]
+  LinearAxis bbox=[0, 5, 25, 270]
+  Title bbox=[25, 5, 245, 0]
+  ToolbarPanel bbox=[270, 5, 30, 270]
diff --git a/bokehjs/test/baselines/Plot__should_allow_max_x_max_plot,_aspect_0.8 b/bokehjs/test/baselines/Plot__should_allow_max_x_max_plot,_aspect_0.8
--- a/bokehjs/test/baselines/Plot__should_allow_max_x_max_plot,_aspect_0.8
+++ b/bokehjs/test/baselines/Plot__should_allow_max_x_max_plot,_aspect_0.8
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 240, 300]
-  LinearAxis bbox=[24, 276, 186, 24]
-  LinearAxis bbox=[0, 5, 24, 271]
-  Title bbox=[24, 5, 186, 0]
-  ToolbarPanel bbox=[210, 5, 30, 271]
+  LinearAxis bbox=[25, 275, 185, 25]
+  LinearAxis bbox=[0, 5, 25, 270]
+  Title bbox=[25, 5, 185, 0]
+  ToolbarPanel bbox=[210, 5, 30, 270]
diff --git a/bokehjs/test/baselines/Plot__should_allow_max_x_max_plot,_aspect_1.0 b/bokehjs/test/baselines/Plot__should_allow_max_x_max_plot,_aspect_1.0
--- a/bokehjs/test/baselines/Plot__should_allow_max_x_max_plot,_aspect_1.0
+++ b/bokehjs/test/baselines/Plot__should_allow_max_x_max_plot,_aspect_1.0
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 300, 300]
-  LinearAxis bbox=[24, 276, 246, 24]
-  LinearAxis bbox=[0, 5, 24, 271]
-  Title bbox=[24, 5, 246, 0]
-  ToolbarPanel bbox=[270, 5, 30, 271]
+  LinearAxis bbox=[25, 275, 245, 25]
+  LinearAxis bbox=[0, 5, 25, 270]
+  Title bbox=[25, 5, 245, 0]
+  ToolbarPanel bbox=[270, 5, 30, 270]
diff --git a/bokehjs/test/baselines/Plot__should_allow_max_x_max_plot,_aspect_1.25 b/bokehjs/test/baselines/Plot__should_allow_max_x_max_plot,_aspect_1.25
--- a/bokehjs/test/baselines/Plot__should_allow_max_x_max_plot,_aspect_1.25
+++ b/bokehjs/test/baselines/Plot__should_allow_max_x_max_plot,_aspect_1.25
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 300, 240]
-  LinearAxis bbox=[24, 216, 246, 24]
-  LinearAxis bbox=[0, 5, 24, 211]
-  Title bbox=[24, 5, 246, 0]
-  ToolbarPanel bbox=[270, 5, 30, 211]
+  LinearAxis bbox=[25, 215, 245, 25]
+  LinearAxis bbox=[0, 5, 25, 210]
+  Title bbox=[25, 5, 245, 0]
+  ToolbarPanel bbox=[270, 5, 30, 210]
diff --git a/bokehjs/test/baselines/Plot__should_allow_no_toolbar_and_no_title b/bokehjs/test/baselines/Plot__should_allow_no_toolbar_and_no_title
--- a/bokehjs/test/baselines/Plot__should_allow_no_toolbar_and_no_title
+++ b/bokehjs/test/baselines/Plot__should_allow_no_toolbar_and_no_title
@@ -1,3 +1,3 @@
 Plot bbox=[0, 0, 200, 200]
-  LinearAxis bbox=[24, 176, 171, 24]
-  LinearAxis bbox=[0, 5, 24, 171]
+  LinearAxis bbox=[25, 175, 170, 25]
+  LinearAxis bbox=[0, 5, 25, 170]
diff --git a/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_above_with_title b/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_above_with_title
--- a/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_above_with_title
+++ b/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_above_with_title
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 200, 200]
-  LinearAxis bbox=[24, 176, 171, 24]
-  LinearAxis bbox=[0, 30, 24, 146]
-  Title bbox=[24, 0, 86, 28]
+  LinearAxis bbox=[25, 175, 170, 25]
+  LinearAxis bbox=[0, 30, 25, 145]
+  Title bbox=[25, 0, 85, 28]
   ToolbarPanel bbox=[110, 0, 85, 30]
diff --git a/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_above_without_title b/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_above_without_title
--- a/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_above_without_title
+++ b/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_above_without_title
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 200, 200]
-  LinearAxis bbox=[24, 176, 171, 24]
-  LinearAxis bbox=[0, 30, 24, 146]
-  Title bbox=[24, 0, 86, 0]
+  LinearAxis bbox=[25, 175, 170, 25]
+  LinearAxis bbox=[0, 30, 25, 145]
+  Title bbox=[25, 0, 85, 0]
   ToolbarPanel bbox=[110, 0, 85, 30]
diff --git a/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_below_with_title b/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_below_with_title
--- a/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_below_with_title
+++ b/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_below_with_title
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 200, 200]
-  LinearAxis bbox=[24, 146, 171, 24]
-  LinearAxis bbox=[0, 5, 24, 141]
-  Title bbox=[24, 170, 86, 28]
+  LinearAxis bbox=[25, 145, 170, 25]
+  LinearAxis bbox=[0, 5, 25, 140]
+  Title bbox=[25, 170, 85, 28]
   ToolbarPanel bbox=[110, 170, 85, 30]
diff --git a/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_below_without_title b/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_below_without_title
--- a/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_below_without_title
+++ b/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_below_without_title
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 200, 200]
-  LinearAxis bbox=[24, 146, 171, 24]
-  LinearAxis bbox=[0, 5, 24, 141]
-  Title bbox=[24, 170, 86, 0]
+  LinearAxis bbox=[25, 145, 170, 25]
+  LinearAxis bbox=[0, 5, 25, 140]
+  Title bbox=[25, 170, 85, 0]
   ToolbarPanel bbox=[110, 170, 85, 30]
diff --git a/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_left_with_title b/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_left_with_title
--- a/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_left_with_title
+++ b/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_left_with_title
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 200, 200]
-  LinearAxis bbox=[54, 176, 141, 24]
-  LinearAxis bbox=[30, 5, 24, 171]
-  Title bbox=[0, 90, 28, 86]
+  LinearAxis bbox=[55, 175, 140, 25]
+  LinearAxis bbox=[30, 5, 25, 170]
+  Title bbox=[0, 90, 28, 85]
   ToolbarPanel bbox=[0, 5, 30, 85]
diff --git a/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_left_without_title b/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_left_without_title
--- a/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_left_without_title
+++ b/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_left_without_title
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 200, 200]
-  LinearAxis bbox=[54, 176, 141, 24]
-  LinearAxis bbox=[30, 5, 24, 171]
-  Title bbox=[0, 90, 0, 86]
+  LinearAxis bbox=[55, 175, 140, 25]
+  LinearAxis bbox=[30, 5, 25, 170]
+  Title bbox=[0, 90, 0, 85]
   ToolbarPanel bbox=[0, 5, 30, 85]
diff --git a/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_right_with_title b/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_right_with_title
--- a/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_right_with_title
+++ b/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_right_with_title
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 200, 200]
-  LinearAxis bbox=[24, 176, 146, 24]
-  LinearAxis bbox=[0, 5, 24, 171]
-  Title bbox=[170, 90, 28, 86]
+  LinearAxis bbox=[25, 175, 145, 25]
+  LinearAxis bbox=[0, 5, 25, 170]
+  Title bbox=[170, 90, 28, 85]
   ToolbarPanel bbox=[170, 5, 30, 85]
diff --git a/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_right_without_title b/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_right_without_title
--- a/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_right_without_title
+++ b/bokehjs/test/baselines/Plot__should_allow_toolbar_placement_right_without_title
@@ -1,5 +1,5 @@
 Plot bbox=[0, 0, 200, 200]
-  LinearAxis bbox=[24, 176, 146, 24]
-  LinearAxis bbox=[0, 5, 24, 171]
-  Title bbox=[170, 90, 0, 86]
+  LinearAxis bbox=[25, 175, 145, 25]
+  LinearAxis bbox=[0, 5, 25, 170]
+  Title bbox=[170, 90, 0, 85]
   ToolbarPanel bbox=[170, 5, 30, 85]
diff --git a/bokehjs/test/baselines/Plot__with_webgl_backend__should_allow_empty_circle_glyphs b/bokehjs/test/baselines/Plot__with_webgl_backend__should_allow_empty_circle_glyphs
--- a/bokehjs/test/baselines/Plot__with_webgl_backend__should_allow_empty_circle_glyphs
+++ b/bokehjs/test/baselines/Plot__with_webgl_backend__should_allow_empty_circle_glyphs
@@ -1,3 +1,3 @@
 Plot bbox=[0, 0, 200, 200]
-  LinearAxis bbox=[18, 176, 177, 24]
-  LinearAxis bbox=[0, 5, 18, 171]
+  LinearAxis bbox=[18, 175, 177, 25]
+  LinearAxis bbox=[0, 5, 18, 170]
diff --git a/bokehjs/test/baselines/Plot__with_webgl_backend__should_allow_empty_line_glyphs b/bokehjs/test/baselines/Plot__with_webgl_backend__should_allow_empty_line_glyphs
--- a/bokehjs/test/baselines/Plot__with_webgl_backend__should_allow_empty_line_glyphs
+++ b/bokehjs/test/baselines/Plot__with_webgl_backend__should_allow_empty_line_glyphs
@@ -1,3 +1,3 @@
 Plot bbox=[0, 0, 200, 200]
-  LinearAxis bbox=[18, 176, 177, 24]
-  LinearAxis bbox=[0, 5, 18, 171]
+  LinearAxis bbox=[18, 175, 177, 25]
+  LinearAxis bbox=[0, 5, 18, 170]
diff --git a/bokehjs/test/baselines/Tabs__should_allow_tabs_header_location_above b/bokehjs/test/baselines/Tabs__should_allow_tabs_header_location_above
--- a/bokehjs/test/baselines/Tabs__should_allow_tabs_header_location_above
+++ b/bokehjs/test/baselines/Tabs__should_allow_tabs_header_location_above
@@ -1,13 +1,13 @@
 Tabs bbox=[0, 0, 200, 230]
   Plot bbox=[0, 30, 200, 200]
-    LinearAxis bbox=[24, 176, 171, 24]
-    LinearAxis bbox=[0, 5, 24, 171]
-    Title bbox=[24, 5, 171, 0]
+    LinearAxis bbox=[25, 175, 170, 25]
+    LinearAxis bbox=[0, 5, 25, 170]
+    Title bbox=[25, 5, 170, 0]
   Plot bbox=[0, 30, 200, 200]
-    LinearAxis bbox=[24, 176, 171, 24]
-    LinearAxis bbox=[0, 5, 24, 171]
-    Title bbox=[24, 5, 171, 0]
+    LinearAxis bbox=[25, 175, 170, 25]
+    LinearAxis bbox=[0, 5, 25, 170]
+    Title bbox=[25, 5, 170, 0]
   Plot bbox=[0, 30, 200, 200]
-    LinearAxis bbox=[24, 176, 171, 24]
-    LinearAxis bbox=[0, 5, 24, 171]
-    Title bbox=[24, 5, 171, 0]
+    LinearAxis bbox=[25, 175, 170, 25]
+    LinearAxis bbox=[0, 5, 25, 170]
+    Title bbox=[25, 5, 170, 0]
diff --git a/bokehjs/test/baselines/Tabs__should_allow_tabs_header_location_below b/bokehjs/test/baselines/Tabs__should_allow_tabs_header_location_below
--- a/bokehjs/test/baselines/Tabs__should_allow_tabs_header_location_below
+++ b/bokehjs/test/baselines/Tabs__should_allow_tabs_header_location_below
@@ -1,13 +1,13 @@
 Tabs bbox=[0, 0, 200, 230]
   Plot bbox=[0, 0, 200, 200]
-    LinearAxis bbox=[24, 176, 171, 24]
-    LinearAxis bbox=[0, 5, 24, 171]
-    Title bbox=[24, 5, 171, 0]
+    LinearAxis bbox=[25, 175, 170, 25]
+    LinearAxis bbox=[0, 5, 25, 170]
+    Title bbox=[25, 5, 170, 0]
   Plot bbox=[0, 0, 200, 200]
-    LinearAxis bbox=[24, 176, 171, 24]
-    LinearAxis bbox=[0, 5, 24, 171]
-    Title bbox=[24, 5, 171, 0]
+    LinearAxis bbox=[25, 175, 170, 25]
+    LinearAxis bbox=[0, 5, 25, 170]
+    Title bbox=[25, 5, 170, 0]
   Plot bbox=[0, 0, 200, 200]
-    LinearAxis bbox=[24, 176, 171, 24]
-    LinearAxis bbox=[0, 5, 24, 171]
-    Title bbox=[24, 5, 171, 0]
+    LinearAxis bbox=[25, 175, 170, 25]
+    LinearAxis bbox=[0, 5, 25, 170]
+    Title bbox=[25, 5, 170, 0]
diff --git a/bokehjs/test/baselines/Tabs__should_allow_tabs_header_location_left b/bokehjs/test/baselines/Tabs__should_allow_tabs_header_location_left
--- a/bokehjs/test/baselines/Tabs__should_allow_tabs_header_location_left
+++ b/bokehjs/test/baselines/Tabs__should_allow_tabs_header_location_left
@@ -1,13 +1,13 @@
 Tabs bbox=[0, 0, 253, 200]
   Plot bbox=[53, 0, 200, 200]
-    LinearAxis bbox=[24, 176, 171, 24]
-    LinearAxis bbox=[0, 5, 24, 171]
-    Title bbox=[24, 5, 171, 0]
+    LinearAxis bbox=[25, 175, 170, 25]
+    LinearAxis bbox=[0, 5, 25, 170]
+    Title bbox=[25, 5, 170, 0]
   Plot bbox=[53, 0, 200, 200]
-    LinearAxis bbox=[24, 176, 171, 24]
-    LinearAxis bbox=[0, 5, 24, 171]
-    Title bbox=[24, 5, 171, 0]
+    LinearAxis bbox=[25, 175, 170, 25]
+    LinearAxis bbox=[0, 5, 25, 170]
+    Title bbox=[25, 5, 170, 0]
   Plot bbox=[53, 0, 200, 200]
-    LinearAxis bbox=[24, 176, 171, 24]
-    LinearAxis bbox=[0, 5, 24, 171]
-    Title bbox=[24, 5, 171, 0]
+    LinearAxis bbox=[25, 175, 170, 25]
+    LinearAxis bbox=[0, 5, 25, 170]
+    Title bbox=[25, 5, 170, 0]
diff --git a/bokehjs/test/baselines/Tabs__should_allow_tabs_header_location_right b/bokehjs/test/baselines/Tabs__should_allow_tabs_header_location_right
--- a/bokehjs/test/baselines/Tabs__should_allow_tabs_header_location_right
+++ b/bokehjs/test/baselines/Tabs__should_allow_tabs_header_location_right
@@ -1,13 +1,13 @@
 Tabs bbox=[0, 0, 253, 200]
   Plot bbox=[0, 0, 200, 200]
-    LinearAxis bbox=[24, 176, 171, 24]
-    LinearAxis bbox=[0, 5, 24, 171]
-    Title bbox=[24, 5, 171, 0]
+    LinearAxis bbox=[25, 175, 170, 25]
+    LinearAxis bbox=[0, 5, 25, 170]
+    Title bbox=[25, 5, 170, 0]
   Plot bbox=[0, 0, 200, 200]
-    LinearAxis bbox=[24, 176, 171, 24]
-    LinearAxis bbox=[0, 5, 24, 171]
-    Title bbox=[24, 5, 171, 0]
+    LinearAxis bbox=[25, 175, 170, 25]
+    LinearAxis bbox=[0, 5, 25, 170]
+    Title bbox=[25, 5, 170, 0]
   Plot bbox=[0, 0, 200, 200]
-    LinearAxis bbox=[24, 176, 171, 24]
-    LinearAxis bbox=[0, 5, 24, 171]
-    Title bbox=[24, 5, 171, 0]
+    LinearAxis bbox=[25, 175, 170, 25]
+    LinearAxis bbox=[0, 5, 25, 170]
+    Title bbox=[25, 5, 170, 0]
diff --git a/bokehjs/test/baselines/ToolbarBox__should_allow_placement_above_a_figure b/bokehjs/test/baselines/ToolbarBox__should_allow_placement_above_a_figure
--- a/bokehjs/test/baselines/ToolbarBox__should_allow_placement_above_a_figure
+++ b/bokehjs/test/baselines/ToolbarBox__should_allow_placement_above_a_figure
@@ -2,6 +2,6 @@ Column bbox=[0, 0, 250, 280]
   ToolbarBox bbox=[0, 0, 250, 30]
     Toolbar
   Plot bbox=[0, 30, 250, 250]
-    LinearAxis bbox=[24, 226, 221, 24]
-    LinearAxis bbox=[0, 5, 24, 221]
-    Title bbox=[24, 5, 221, 0]
+    LinearAxis bbox=[25, 225, 220, 25]
+    LinearAxis bbox=[0, 5, 25, 220]
+    Title bbox=[25, 5, 220, 0]
diff --git a/bokehjs/test/baselines/ToolbarBox__should_allow_placement_below_a_figure b/bokehjs/test/baselines/ToolbarBox__should_allow_placement_below_a_figure
--- a/bokehjs/test/baselines/ToolbarBox__should_allow_placement_below_a_figure
+++ b/bokehjs/test/baselines/ToolbarBox__should_allow_placement_below_a_figure
@@ -1,7 +1,7 @@
 Column bbox=[0, 0, 250, 280]
   Plot bbox=[0, 0, 250, 250]
-    LinearAxis bbox=[24, 226, 221, 24]
-    LinearAxis bbox=[0, 5, 24, 221]
-    Title bbox=[24, 5, 221, 0]
+    LinearAxis bbox=[25, 225, 220, 25]
+    LinearAxis bbox=[0, 5, 25, 220]
+    Title bbox=[25, 5, 220, 0]
   ToolbarBox bbox=[0, 250, 250, 30]
     Toolbar
diff --git a/bokehjs/test/baselines/ToolbarBox__should_allow_placement_left_of_a_figure b/bokehjs/test/baselines/ToolbarBox__should_allow_placement_left_of_a_figure
--- a/bokehjs/test/baselines/ToolbarBox__should_allow_placement_left_of_a_figure
+++ b/bokehjs/test/baselines/ToolbarBox__should_allow_placement_left_of_a_figure
@@ -2,6 +2,6 @@ Row bbox=[0, 0, 280, 250]
   ToolbarBox bbox=[0, 0, 30, 250]
     Toolbar
   Plot bbox=[30, 0, 250, 250]
-    LinearAxis bbox=[24, 226, 221, 24]
-    LinearAxis bbox=[0, 5, 24, 221]
-    Title bbox=[24, 5, 221, 0]
+    LinearAxis bbox=[25, 225, 220, 25]
+    LinearAxis bbox=[0, 5, 25, 220]
+    Title bbox=[25, 5, 220, 0]
diff --git a/bokehjs/test/baselines/ToolbarBox__should_allow_placement_right_of_a_figure b/bokehjs/test/baselines/ToolbarBox__should_allow_placement_right_of_a_figure
--- a/bokehjs/test/baselines/ToolbarBox__should_allow_placement_right_of_a_figure
+++ b/bokehjs/test/baselines/ToolbarBox__should_allow_placement_right_of_a_figure
@@ -1,7 +1,7 @@
 Row bbox=[0, 0, 280, 250]
   Plot bbox=[0, 0, 250, 250]
-    LinearAxis bbox=[24, 226, 221, 24]
-    LinearAxis bbox=[0, 5, 24, 221]
-    Title bbox=[24, 5, 221, 0]
+    LinearAxis bbox=[25, 225, 220, 25]
+    LinearAxis bbox=[0, 5, 25, 220]
+    Title bbox=[25, 5, 220, 0]
   ToolbarBox bbox=[250, 0, 30, 250]
     Toolbar
diff --git a/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=above b/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=above
--- a/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=above
+++ b/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=above
@@ -3,38 +3,38 @@ Column bbox=[0, 0, 600, 630]
     ProxyToolbar
   GridBox bbox=[0, 30, 600, 600]
     Plot bbox=[0, 0, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 38]
-      LinearAxis bbox=[40, 5, 38, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 38]
+      LinearAxis bbox=[41, 5, 38, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[200, 0, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 58]
-      LinearAxis bbox=[40, 5, 38, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 59]
+      LinearAxis bbox=[41, 5, 38, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[400, 0, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 78]
-      LinearAxis bbox=[40, 5, 38, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 79]
+      LinearAxis bbox=[41, 5, 38, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[0, 200, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 38]
-      LinearAxis bbox=[20, 5, 58, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 38]
+      LinearAxis bbox=[20, 5, 59, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[200, 200, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 58]
-      LinearAxis bbox=[20, 5, 58, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 59]
+      LinearAxis bbox=[20, 5, 59, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[400, 200, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 78]
-      LinearAxis bbox=[20, 5, 58, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 79]
+      LinearAxis bbox=[20, 5, 59, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[0, 400, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 38]
-      LinearAxis bbox=[0, 5, 78, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 38]
+      LinearAxis bbox=[0, 5, 79, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[200, 400, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 58]
-      LinearAxis bbox=[0, 5, 78, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 59]
+      LinearAxis bbox=[0, 5, 79, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[400, 400, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 78]
-      LinearAxis bbox=[0, 5, 78, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 79]
+      LinearAxis bbox=[0, 5, 79, 116]
+      Title bbox=[79, 5, 116, 0]
diff --git a/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=below b/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=below
--- a/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=below
+++ b/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=below
@@ -1,40 +1,40 @@
 Column bbox=[0, 0, 600, 630]
   GridBox bbox=[0, 0, 600, 600]
     Plot bbox=[0, 0, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 38]
-      LinearAxis bbox=[40, 5, 38, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 38]
+      LinearAxis bbox=[41, 5, 38, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[200, 0, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 58]
-      LinearAxis bbox=[40, 5, 38, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 59]
+      LinearAxis bbox=[41, 5, 38, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[400, 0, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 78]
-      LinearAxis bbox=[40, 5, 38, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 79]
+      LinearAxis bbox=[41, 5, 38, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[0, 200, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 38]
-      LinearAxis bbox=[20, 5, 58, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 38]
+      LinearAxis bbox=[20, 5, 59, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[200, 200, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 58]
-      LinearAxis bbox=[20, 5, 58, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 59]
+      LinearAxis bbox=[20, 5, 59, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[400, 200, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 78]
-      LinearAxis bbox=[20, 5, 58, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 79]
+      LinearAxis bbox=[20, 5, 59, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[0, 400, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 38]
-      LinearAxis bbox=[0, 5, 78, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 38]
+      LinearAxis bbox=[0, 5, 79, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[200, 400, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 58]
-      LinearAxis bbox=[0, 5, 78, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 59]
+      LinearAxis bbox=[0, 5, 79, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[400, 400, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 78]
-      LinearAxis bbox=[0, 5, 78, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 79]
+      LinearAxis bbox=[0, 5, 79, 116]
+      Title bbox=[79, 5, 116, 0]
   ToolbarBox bbox=[0, 600, 600, 30]
     ProxyToolbar
diff --git a/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=left b/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=left
--- a/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=left
+++ b/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=left
@@ -3,38 +3,38 @@ Row bbox=[0, 0, 630, 600]
     ProxyToolbar
   GridBox bbox=[30, 0, 600, 600]
     Plot bbox=[0, 0, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 38]
-      LinearAxis bbox=[40, 5, 38, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 38]
+      LinearAxis bbox=[41, 5, 38, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[200, 0, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 58]
-      LinearAxis bbox=[40, 5, 38, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 59]
+      LinearAxis bbox=[41, 5, 38, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[400, 0, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 78]
-      LinearAxis bbox=[40, 5, 38, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 79]
+      LinearAxis bbox=[41, 5, 38, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[0, 200, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 38]
-      LinearAxis bbox=[20, 5, 58, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 38]
+      LinearAxis bbox=[20, 5, 59, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[200, 200, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 58]
-      LinearAxis bbox=[20, 5, 58, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 59]
+      LinearAxis bbox=[20, 5, 59, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[400, 200, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 78]
-      LinearAxis bbox=[20, 5, 58, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 79]
+      LinearAxis bbox=[20, 5, 59, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[0, 400, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 38]
-      LinearAxis bbox=[0, 5, 78, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 38]
+      LinearAxis bbox=[0, 5, 79, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[200, 400, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 58]
-      LinearAxis bbox=[0, 5, 78, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 59]
+      LinearAxis bbox=[0, 5, 79, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[400, 400, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 78]
-      LinearAxis bbox=[0, 5, 78, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 79]
+      LinearAxis bbox=[0, 5, 79, 116]
+      Title bbox=[79, 5, 116, 0]
diff --git a/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=null b/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=null
--- a/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=null
+++ b/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=null
@@ -1,37 +1,37 @@
 GridBox bbox=[0, 0, 600, 600]
   Plot bbox=[0, 0, 200, 200]
-    LinearAxis bbox=[78, 122, 117, 38]
-    LinearAxis bbox=[40, 5, 38, 117]
-    Title bbox=[78, 5, 117, 0]
+    LinearAxis bbox=[79, 121, 116, 38]
+    LinearAxis bbox=[41, 5, 38, 116]
+    Title bbox=[79, 5, 116, 0]
   Plot bbox=[200, 0, 200, 200]
-    LinearAxis bbox=[78, 122, 117, 58]
-    LinearAxis bbox=[40, 5, 38, 117]
-    Title bbox=[78, 5, 117, 0]
+    LinearAxis bbox=[79, 121, 116, 59]
+    LinearAxis bbox=[41, 5, 38, 116]
+    Title bbox=[79, 5, 116, 0]
   Plot bbox=[400, 0, 200, 200]
-    LinearAxis bbox=[78, 122, 117, 78]
-    LinearAxis bbox=[40, 5, 38, 117]
-    Title bbox=[78, 5, 117, 0]
+    LinearAxis bbox=[79, 121, 116, 79]
+    LinearAxis bbox=[41, 5, 38, 116]
+    Title bbox=[79, 5, 116, 0]
   Plot bbox=[0, 200, 200, 200]
-    LinearAxis bbox=[78, 122, 117, 38]
-    LinearAxis bbox=[20, 5, 58, 117]
-    Title bbox=[78, 5, 117, 0]
+    LinearAxis bbox=[79, 121, 116, 38]
+    LinearAxis bbox=[20, 5, 59, 116]
+    Title bbox=[79, 5, 116, 0]
   Plot bbox=[200, 200, 200, 200]
-    LinearAxis bbox=[78, 122, 117, 58]
-    LinearAxis bbox=[20, 5, 58, 117]
-    Title bbox=[78, 5, 117, 0]
+    LinearAxis bbox=[79, 121, 116, 59]
+    LinearAxis bbox=[20, 5, 59, 116]
+    Title bbox=[79, 5, 116, 0]
   Plot bbox=[400, 200, 200, 200]
-    LinearAxis bbox=[78, 122, 117, 78]
-    LinearAxis bbox=[20, 5, 58, 117]
-    Title bbox=[78, 5, 117, 0]
+    LinearAxis bbox=[79, 121, 116, 79]
+    LinearAxis bbox=[20, 5, 59, 116]
+    Title bbox=[79, 5, 116, 0]
   Plot bbox=[0, 400, 200, 200]
-    LinearAxis bbox=[78, 122, 117, 38]
-    LinearAxis bbox=[0, 5, 78, 117]
-    Title bbox=[78, 5, 117, 0]
+    LinearAxis bbox=[79, 121, 116, 38]
+    LinearAxis bbox=[0, 5, 79, 116]
+    Title bbox=[79, 5, 116, 0]
   Plot bbox=[200, 400, 200, 200]
-    LinearAxis bbox=[78, 122, 117, 58]
-    LinearAxis bbox=[0, 5, 78, 117]
-    Title bbox=[78, 5, 117, 0]
+    LinearAxis bbox=[79, 121, 116, 59]
+    LinearAxis bbox=[0, 5, 79, 116]
+    Title bbox=[79, 5, 116, 0]
   Plot bbox=[400, 400, 200, 200]
-    LinearAxis bbox=[78, 122, 117, 78]
-    LinearAxis bbox=[0, 5, 78, 117]
-    Title bbox=[78, 5, 117, 0]
+    LinearAxis bbox=[79, 121, 116, 79]
+    LinearAxis bbox=[0, 5, 79, 116]
+    Title bbox=[79, 5, 116, 0]
diff --git a/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=right b/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=right
--- a/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=right
+++ b/bokehjs/test/baselines/gridplot()__should_align_axes_when_toolbar_location=right
@@ -1,40 +1,40 @@
 Row bbox=[0, 0, 630, 600]
   GridBox bbox=[0, 0, 600, 600]
     Plot bbox=[0, 0, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 38]
-      LinearAxis bbox=[40, 5, 38, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 38]
+      LinearAxis bbox=[41, 5, 38, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[200, 0, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 58]
-      LinearAxis bbox=[40, 5, 38, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 59]
+      LinearAxis bbox=[41, 5, 38, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[400, 0, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 78]
-      LinearAxis bbox=[40, 5, 38, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 79]
+      LinearAxis bbox=[41, 5, 38, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[0, 200, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 38]
-      LinearAxis bbox=[20, 5, 58, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 38]
+      LinearAxis bbox=[20, 5, 59, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[200, 200, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 58]
-      LinearAxis bbox=[20, 5, 58, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 59]
+      LinearAxis bbox=[20, 5, 59, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[400, 200, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 78]
-      LinearAxis bbox=[20, 5, 58, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 79]
+      LinearAxis bbox=[20, 5, 59, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[0, 400, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 38]
-      LinearAxis bbox=[0, 5, 78, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 38]
+      LinearAxis bbox=[0, 5, 79, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[200, 400, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 58]
-      LinearAxis bbox=[0, 5, 78, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 59]
+      LinearAxis bbox=[0, 5, 79, 116]
+      Title bbox=[79, 5, 116, 0]
     Plot bbox=[400, 400, 200, 200]
-      LinearAxis bbox=[78, 122, 117, 78]
-      LinearAxis bbox=[0, 5, 78, 117]
-      Title bbox=[78, 5, 117, 0]
+      LinearAxis bbox=[79, 121, 116, 79]
+      LinearAxis bbox=[0, 5, 79, 116]
+      Title bbox=[79, 5, 116, 0]
   ToolbarBox bbox=[600, 0, 30, 600]
     ProxyToolbar
diff --git a/bokehjs/test/baselines/linux/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=center.png b/bokehjs/test/baselines/linux/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=center.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=center.png differ
diff --git a/bokehjs/test/baselines/linux/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=end.png b/bokehjs/test/baselines/linux/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=end.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=end.png differ
diff --git a/bokehjs/test/baselines/linux/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=start.png b/bokehjs/test/baselines/linux/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=start.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=start.png differ
diff --git a/bokehjs/test/baselines/linux/3x3_GridBox__fixed_and_1_x-max_spacers,_c2_auto.png b/bokehjs/test/baselines/linux/3x3_GridBox__fixed_and_1_x-max_spacers,_c2_auto.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/3x3_GridBox__fixed_and_1_x-max_spacers,_c2_auto.png differ
diff --git a/bokehjs/test/baselines/linux/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_auto.png b/bokehjs/test/baselines/linux/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_auto.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_auto.png differ
diff --git a/bokehjs/test/baselines/linux/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_flex=2.png b/bokehjs/test/baselines/linux/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_flex=2.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_flex=2.png differ
diff --git a/bokehjs/test/baselines/linux/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2.png b/bokehjs/test/baselines/linux/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2.png differ
diff --git a/bokehjs/test/baselines/linux/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2_align=end.png b/bokehjs/test/baselines/linux/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2_align=end.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2_align=end.png differ
diff --git a/bokehjs/test/baselines/linux/3x3_GridBox__fixed_spacers_50px_x_50px,_spacing_5px.png b/bokehjs/test/baselines/linux/3x3_GridBox__fixed_spacers_50px_x_50px,_spacing_5px.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/3x3_GridBox__fixed_spacers_50px_x_50px,_spacing_5px.png differ
diff --git a/bokehjs/test/baselines/linux/3x3_GridBox__fixed_spacers_50px_x_50px,_vspacing_5px,_hspacing_10px.png b/bokehjs/test/baselines/linux/3x3_GridBox__fixed_spacers_50px_x_50px,_vspacing_5px,_hspacing_10px.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/3x3_GridBox__fixed_spacers_50px_x_50px,_vspacing_5px,_hspacing_10px.png differ
diff --git a/bokehjs/test/baselines/linux/3x3_GridBox__fixed_spacers_50px_x_50px.png b/bokehjs/test/baselines/linux/3x3_GridBox__fixed_spacers_50px_x_50px.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/3x3_GridBox__fixed_spacers_50px_x_50px.png differ
diff --git a/bokehjs/test/baselines/linux/Bug__in_issue_#9522__disallows_arrow_to_be_positioned_correctly_in_stacked_layouts.png b/bokehjs/test/baselines/linux/Bug__in_issue_#9522__disallows_arrow_to_be_positioned_correctly_in_stacked_layouts.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Bug__in_issue_#9522__disallows_arrow_to_be_positioned_correctly_in_stacked_layouts.png differ
diff --git a/bokehjs/test/baselines/linux/Bug__in_issue_#9703__disallows_ImageURL_glyph_to_set_anchor_and_angle_at_the_same_time.png b/bokehjs/test/baselines/linux/Bug__in_issue_#9703__disallows_ImageURL_glyph_to_set_anchor_and_angle_at_the_same_time.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Bug__in_issue_#9703__disallows_ImageURL_glyph_to_set_anchor_and_angle_at_the_same_time.png differ
diff --git a/bokehjs/test/baselines/linux/Bug__in_issue_#9724__makes_automatic_padding_in_data_ranges_inconsistent.png b/bokehjs/test/baselines/linux/Bug__in_issue_#9724__makes_automatic_padding_in_data_ranges_inconsistent.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Bug__in_issue_#9724__makes_automatic_padding_in_data_ranges_inconsistent.png differ
diff --git a/bokehjs/test/baselines/linux/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_fill_color_==_null.png b/bokehjs/test/baselines/linux/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_fill_color_==_null.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_fill_color_==_null.png differ
diff --git a/bokehjs/test/baselines/linux/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_line_color_==_null.png b/bokehjs/test/baselines/linux/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_line_color_==_null.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_line_color_==_null.png differ
diff --git a/bokehjs/test/baselines/linux/Bug__in_issue_#9879__disallows_to_change_FactorRange_to_a_lower_dimension_with_a_different_number_of_factors.png b/bokehjs/test/baselines/linux/Bug__in_issue_#9879__disallows_to_change_FactorRange_to_a_lower_dimension_with_a_different_number_of_factors.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Bug__in_issue_#9879__disallows_to_change_FactorRange_to_a_lower_dimension_with_a_different_number_of_factors.png differ
diff --git a/bokehjs/test/baselines/linux/GridBox__should_allow_20x20_grids_of_25x25_px_spacers.png b/bokehjs/test/baselines/linux/GridBox__should_allow_20x20_grids_of_25x25_px_spacers.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/GridBox__should_allow_20x20_grids_of_25x25_px_spacers.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout.png b/bokehjs/test/baselines/linux/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout_with_a_title.png b/bokehjs/test/baselines/linux/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout_with_a_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout_with_a_title.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_fixed_frame_300px_x_300px.png b/bokehjs/test/baselines/linux/Plot__should_allow_fixed_frame_300px_x_300px.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_fixed_frame_300px_x_300px.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_fixed_x_fixed_plot.png b/bokehjs/test/baselines/linux/Plot__should_allow_fixed_x_fixed_plot.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_fixed_x_fixed_plot.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_fixed_x_max_plot,_aspect_0.8.png b/bokehjs/test/baselines/linux/Plot__should_allow_fixed_x_max_plot,_aspect_0.8.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_fixed_x_max_plot,_aspect_0.8.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_fixed_x_max_plot,_aspect_1.0.png b/bokehjs/test/baselines/linux/Plot__should_allow_fixed_x_max_plot,_aspect_1.0.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_fixed_x_max_plot,_aspect_1.0.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_fixed_x_max_plot,_aspect_1.25.png b/bokehjs/test/baselines/linux/Plot__should_allow_fixed_x_max_plot,_aspect_1.25.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_fixed_x_max_plot,_aspect_1.25.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_fixed_x_max_plot.png b/bokehjs/test/baselines/linux/Plot__should_allow_fixed_x_max_plot.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_fixed_x_max_plot.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_max_x_fixed_plot,_aspect_0.8.png b/bokehjs/test/baselines/linux/Plot__should_allow_max_x_fixed_plot,_aspect_0.8.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_max_x_fixed_plot,_aspect_0.8.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_max_x_fixed_plot,_aspect_1.0.png b/bokehjs/test/baselines/linux/Plot__should_allow_max_x_fixed_plot,_aspect_1.0.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_max_x_fixed_plot,_aspect_1.0.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_max_x_fixed_plot,_aspect_1.25.png b/bokehjs/test/baselines/linux/Plot__should_allow_max_x_fixed_plot,_aspect_1.25.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_max_x_fixed_plot,_aspect_1.25.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_max_x_fixed_plot.png b/bokehjs/test/baselines/linux/Plot__should_allow_max_x_fixed_plot.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_max_x_fixed_plot.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_max_x_max_plot,_aspect_0.8.png b/bokehjs/test/baselines/linux/Plot__should_allow_max_x_max_plot,_aspect_0.8.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_max_x_max_plot,_aspect_0.8.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_max_x_max_plot,_aspect_1.0.png b/bokehjs/test/baselines/linux/Plot__should_allow_max_x_max_plot,_aspect_1.0.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_max_x_max_plot,_aspect_1.0.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_max_x_max_plot,_aspect_1.25.png b/bokehjs/test/baselines/linux/Plot__should_allow_max_x_max_plot,_aspect_1.25.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_max_x_max_plot,_aspect_1.25.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_max_x_max_plot.png b/bokehjs/test/baselines/linux/Plot__should_allow_max_x_max_plot.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_max_x_max_plot.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_no_toolbar_and_no_title.png b/bokehjs/test/baselines/linux/Plot__should_allow_no_toolbar_and_no_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_no_toolbar_and_no_title.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_above_with_title.png b/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_above_with_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_above_with_title.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_above_without_title.png b/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_above_without_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_above_without_title.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_below_with_title.png b/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_below_with_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_below_with_title.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_below_without_title.png b/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_below_without_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_below_without_title.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_left_with_title.png b/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_left_with_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_left_with_title.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_left_without_title.png b/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_left_without_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_left_without_title.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_right_with_title.png b/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_right_with_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_right_with_title.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_right_without_title.png b/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_right_without_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__should_allow_toolbar_placement_right_without_title.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__with_webgl_backend__should_allow_empty_circle_glyphs.png b/bokehjs/test/baselines/linux/Plot__with_webgl_backend__should_allow_empty_circle_glyphs.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__with_webgl_backend__should_allow_empty_circle_glyphs.png differ
diff --git a/bokehjs/test/baselines/linux/Plot__with_webgl_backend__should_allow_empty_line_glyphs.png b/bokehjs/test/baselines/linux/Plot__with_webgl_backend__should_allow_empty_line_glyphs.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Plot__with_webgl_backend__should_allow_empty_line_glyphs.png differ
diff --git a/bokehjs/test/baselines/linux/Row__should_allow_to_expand_when_child_policy_is_max.png b/bokehjs/test/baselines/linux/Row__should_allow_to_expand_when_child_policy_is_max.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Row__should_allow_to_expand_when_child_policy_is_max.png differ
diff --git a/bokehjs/test/baselines/linux/Rows_of_widgets__should_allow_different_content_and_fixed_height.png b/bokehjs/test/baselines/linux/Rows_of_widgets__should_allow_different_content_and_fixed_height.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Rows_of_widgets__should_allow_different_content_and_fixed_height.png differ
diff --git a/bokehjs/test/baselines/linux/Tabs__should_allow_tabs_header_location_above.png b/bokehjs/test/baselines/linux/Tabs__should_allow_tabs_header_location_above.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Tabs__should_allow_tabs_header_location_above.png differ
diff --git a/bokehjs/test/baselines/linux/Tabs__should_allow_tabs_header_location_below.png b/bokehjs/test/baselines/linux/Tabs__should_allow_tabs_header_location_below.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Tabs__should_allow_tabs_header_location_below.png differ
diff --git a/bokehjs/test/baselines/linux/Tabs__should_allow_tabs_header_location_left.png b/bokehjs/test/baselines/linux/Tabs__should_allow_tabs_header_location_left.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Tabs__should_allow_tabs_header_location_left.png differ
diff --git a/bokehjs/test/baselines/linux/Tabs__should_allow_tabs_header_location_right.png b/bokehjs/test/baselines/linux/Tabs__should_allow_tabs_header_location_right.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Tabs__should_allow_tabs_header_location_right.png differ
diff --git a/bokehjs/test/baselines/linux/ToolbarBox__should_allow_placement_above_a_figure.png b/bokehjs/test/baselines/linux/ToolbarBox__should_allow_placement_above_a_figure.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/ToolbarBox__should_allow_placement_above_a_figure.png differ
diff --git a/bokehjs/test/baselines/linux/ToolbarBox__should_allow_placement_below_a_figure.png b/bokehjs/test/baselines/linux/ToolbarBox__should_allow_placement_below_a_figure.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/ToolbarBox__should_allow_placement_below_a_figure.png differ
diff --git a/bokehjs/test/baselines/linux/ToolbarBox__should_allow_placement_left_of_a_figure.png b/bokehjs/test/baselines/linux/ToolbarBox__should_allow_placement_left_of_a_figure.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/ToolbarBox__should_allow_placement_left_of_a_figure.png differ
diff --git a/bokehjs/test/baselines/linux/ToolbarBox__should_allow_placement_right_of_a_figure.png b/bokehjs/test/baselines/linux/ToolbarBox__should_allow_placement_right_of_a_figure.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/ToolbarBox__should_allow_placement_right_of_a_figure.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_AutocompleteInput.png b/bokehjs/test/baselines/linux/Widgets__should_allow_AutocompleteInput.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_AutocompleteInput.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_Button.png b/bokehjs/test/baselines/linux/Widgets__should_allow_Button.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_Button.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_CheckboxButtonGroup.png b/bokehjs/test/baselines/linux/Widgets__should_allow_CheckboxButtonGroup.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_CheckboxButtonGroup.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_CheckboxGroup.png b/bokehjs/test/baselines/linux/Widgets__should_allow_CheckboxGroup.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_CheckboxGroup.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_DatePicker.png b/bokehjs/test/baselines/linux/Widgets__should_allow_DatePicker.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_DatePicker.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_DateRangeSlider.png b/bokehjs/test/baselines/linux/Widgets__should_allow_DateRangeSlider.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_DateRangeSlider.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_DateSlider.png b/bokehjs/test/baselines/linux/Widgets__should_allow_DateSlider.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_DateSlider.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_Div.png b/bokehjs/test/baselines/linux/Widgets__should_allow_Div.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_Div.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_Dropdown.png b/bokehjs/test/baselines/linux/Widgets__should_allow_Dropdown.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_Dropdown.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_MultiSelect.png b/bokehjs/test/baselines/linux/Widgets__should_allow_MultiSelect.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_MultiSelect.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_Paragraph.png b/bokehjs/test/baselines/linux/Widgets__should_allow_Paragraph.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_Paragraph.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_PreText.png b/bokehjs/test/baselines/linux/Widgets__should_allow_PreText.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_PreText.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_RadioButtonGroup.png b/bokehjs/test/baselines/linux/Widgets__should_allow_RadioButtonGroup.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_RadioButtonGroup.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_RadioGroup.png b/bokehjs/test/baselines/linux/Widgets__should_allow_RadioGroup.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_RadioGroup.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_RangeSlider.png b/bokehjs/test/baselines/linux/Widgets__should_allow_RangeSlider.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_RangeSlider.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_Select.png b/bokehjs/test/baselines/linux/Widgets__should_allow_Select.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_Select.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_Slider.png b/bokehjs/test/baselines/linux/Widgets__should_allow_Slider.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_Slider.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_TextInput.png b/bokehjs/test/baselines/linux/Widgets__should_allow_TextInput.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_TextInput.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_Toggle.png b/bokehjs/test/baselines/linux/Widgets__should_allow_Toggle.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_Toggle.png differ
diff --git a/bokehjs/test/baselines/linux/Widgets__should_allow_split_Dropdown.png b/bokehjs/test/baselines/linux/Widgets__should_allow_split_Dropdown.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/Widgets__should_allow_split_Dropdown.png differ
diff --git a/bokehjs/test/baselines/linux/gridplot()__should_align_axes_when_toolbar_location=above.png b/bokehjs/test/baselines/linux/gridplot()__should_align_axes_when_toolbar_location=above.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/gridplot()__should_align_axes_when_toolbar_location=above.png differ
diff --git a/bokehjs/test/baselines/linux/gridplot()__should_align_axes_when_toolbar_location=below.png b/bokehjs/test/baselines/linux/gridplot()__should_align_axes_when_toolbar_location=below.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/gridplot()__should_align_axes_when_toolbar_location=below.png differ
diff --git a/bokehjs/test/baselines/linux/gridplot()__should_align_axes_when_toolbar_location=left.png b/bokehjs/test/baselines/linux/gridplot()__should_align_axes_when_toolbar_location=left.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/gridplot()__should_align_axes_when_toolbar_location=left.png differ
diff --git a/bokehjs/test/baselines/linux/gridplot()__should_align_axes_when_toolbar_location=null.png b/bokehjs/test/baselines/linux/gridplot()__should_align_axes_when_toolbar_location=null.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/gridplot()__should_align_axes_when_toolbar_location=null.png differ
diff --git a/bokehjs/test/baselines/linux/gridplot()__should_align_axes_when_toolbar_location=right.png b/bokehjs/test/baselines/linux/gridplot()__should_align_axes_when_toolbar_location=right.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/linux/gridplot()__should_align_axes_when_toolbar_location=right.png differ
diff --git a/bokehjs/test/baselines/macos/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=center.png b/bokehjs/test/baselines/macos/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=center.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=center.png differ
diff --git a/bokehjs/test/baselines/macos/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=end.png b/bokehjs/test/baselines/macos/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=end.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=end.png differ
diff --git a/bokehjs/test/baselines/macos/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=start.png b/bokehjs/test/baselines/macos/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=start.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=start.png differ
diff --git a/bokehjs/test/baselines/macos/3x3_GridBox__fixed_and_1_x-max_spacers,_c2_auto.png b/bokehjs/test/baselines/macos/3x3_GridBox__fixed_and_1_x-max_spacers,_c2_auto.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/3x3_GridBox__fixed_and_1_x-max_spacers,_c2_auto.png differ
diff --git a/bokehjs/test/baselines/macos/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_auto.png b/bokehjs/test/baselines/macos/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_auto.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_auto.png differ
diff --git a/bokehjs/test/baselines/macos/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_flex=2.png b/bokehjs/test/baselines/macos/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_flex=2.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_flex=2.png differ
diff --git a/bokehjs/test/baselines/macos/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2.png b/bokehjs/test/baselines/macos/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2.png differ
diff --git a/bokehjs/test/baselines/macos/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2_align=end.png b/bokehjs/test/baselines/macos/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2_align=end.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2_align=end.png differ
diff --git a/bokehjs/test/baselines/macos/3x3_GridBox__fixed_spacers_50px_x_50px,_spacing_5px.png b/bokehjs/test/baselines/macos/3x3_GridBox__fixed_spacers_50px_x_50px,_spacing_5px.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/3x3_GridBox__fixed_spacers_50px_x_50px,_spacing_5px.png differ
diff --git a/bokehjs/test/baselines/macos/3x3_GridBox__fixed_spacers_50px_x_50px,_vspacing_5px,_hspacing_10px.png b/bokehjs/test/baselines/macos/3x3_GridBox__fixed_spacers_50px_x_50px,_vspacing_5px,_hspacing_10px.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/3x3_GridBox__fixed_spacers_50px_x_50px,_vspacing_5px,_hspacing_10px.png differ
diff --git a/bokehjs/test/baselines/macos/3x3_GridBox__fixed_spacers_50px_x_50px.png b/bokehjs/test/baselines/macos/3x3_GridBox__fixed_spacers_50px_x_50px.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/3x3_GridBox__fixed_spacers_50px_x_50px.png differ
diff --git a/bokehjs/test/baselines/macos/Bug__in_issue_#9522__disallows_arrow_to_be_positioned_correctly_in_stacked_layouts.png b/bokehjs/test/baselines/macos/Bug__in_issue_#9522__disallows_arrow_to_be_positioned_correctly_in_stacked_layouts.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Bug__in_issue_#9522__disallows_arrow_to_be_positioned_correctly_in_stacked_layouts.png differ
diff --git a/bokehjs/test/baselines/macos/Bug__in_issue_#9703__disallows_ImageURL_glyph_to_set_anchor_and_angle_at_the_same_time.png b/bokehjs/test/baselines/macos/Bug__in_issue_#9703__disallows_ImageURL_glyph_to_set_anchor_and_angle_at_the_same_time.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Bug__in_issue_#9703__disallows_ImageURL_glyph_to_set_anchor_and_angle_at_the_same_time.png differ
diff --git a/bokehjs/test/baselines/macos/Bug__in_issue_#9724__makes_automatic_padding_in_data_ranges_inconsistent.png b/bokehjs/test/baselines/macos/Bug__in_issue_#9724__makes_automatic_padding_in_data_ranges_inconsistent.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Bug__in_issue_#9724__makes_automatic_padding_in_data_ranges_inconsistent.png differ
diff --git a/bokehjs/test/baselines/macos/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_fill_color_==_null.png b/bokehjs/test/baselines/macos/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_fill_color_==_null.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_fill_color_==_null.png differ
diff --git a/bokehjs/test/baselines/macos/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_line_color_==_null.png b/bokehjs/test/baselines/macos/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_line_color_==_null.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_line_color_==_null.png differ
diff --git a/bokehjs/test/baselines/macos/Bug__in_issue_#9879__disallows_to_change_FactorRange_to_a_lower_dimension_with_a_different_number_of_factors.png b/bokehjs/test/baselines/macos/Bug__in_issue_#9879__disallows_to_change_FactorRange_to_a_lower_dimension_with_a_different_number_of_factors.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Bug__in_issue_#9879__disallows_to_change_FactorRange_to_a_lower_dimension_with_a_different_number_of_factors.png differ
diff --git a/bokehjs/test/baselines/macos/GridBox__should_allow_20x20_grids_of_25x25_px_spacers.png b/bokehjs/test/baselines/macos/GridBox__should_allow_20x20_grids_of_25x25_px_spacers.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/GridBox__should_allow_20x20_grids_of_25x25_px_spacers.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout.png b/bokehjs/test/baselines/macos/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout_with_a_title.png b/bokehjs/test/baselines/macos/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout_with_a_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout_with_a_title.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_fixed_frame_300px_x_300px.png b/bokehjs/test/baselines/macos/Plot__should_allow_fixed_frame_300px_x_300px.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_fixed_frame_300px_x_300px.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_fixed_x_fixed_plot.png b/bokehjs/test/baselines/macos/Plot__should_allow_fixed_x_fixed_plot.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_fixed_x_fixed_plot.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_fixed_x_max_plot,_aspect_0.8.png b/bokehjs/test/baselines/macos/Plot__should_allow_fixed_x_max_plot,_aspect_0.8.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_fixed_x_max_plot,_aspect_0.8.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_fixed_x_max_plot,_aspect_1.0.png b/bokehjs/test/baselines/macos/Plot__should_allow_fixed_x_max_plot,_aspect_1.0.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_fixed_x_max_plot,_aspect_1.0.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_fixed_x_max_plot,_aspect_1.25.png b/bokehjs/test/baselines/macos/Plot__should_allow_fixed_x_max_plot,_aspect_1.25.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_fixed_x_max_plot,_aspect_1.25.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_fixed_x_max_plot.png b/bokehjs/test/baselines/macos/Plot__should_allow_fixed_x_max_plot.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_fixed_x_max_plot.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_max_x_fixed_plot,_aspect_0.8.png b/bokehjs/test/baselines/macos/Plot__should_allow_max_x_fixed_plot,_aspect_0.8.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_max_x_fixed_plot,_aspect_0.8.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_max_x_fixed_plot,_aspect_1.0.png b/bokehjs/test/baselines/macos/Plot__should_allow_max_x_fixed_plot,_aspect_1.0.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_max_x_fixed_plot,_aspect_1.0.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_max_x_fixed_plot,_aspect_1.25.png b/bokehjs/test/baselines/macos/Plot__should_allow_max_x_fixed_plot,_aspect_1.25.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_max_x_fixed_plot,_aspect_1.25.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_max_x_fixed_plot.png b/bokehjs/test/baselines/macos/Plot__should_allow_max_x_fixed_plot.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_max_x_fixed_plot.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_max_x_max_plot,_aspect_0.8.png b/bokehjs/test/baselines/macos/Plot__should_allow_max_x_max_plot,_aspect_0.8.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_max_x_max_plot,_aspect_0.8.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_max_x_max_plot,_aspect_1.0.png b/bokehjs/test/baselines/macos/Plot__should_allow_max_x_max_plot,_aspect_1.0.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_max_x_max_plot,_aspect_1.0.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_max_x_max_plot,_aspect_1.25.png b/bokehjs/test/baselines/macos/Plot__should_allow_max_x_max_plot,_aspect_1.25.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_max_x_max_plot,_aspect_1.25.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_max_x_max_plot.png b/bokehjs/test/baselines/macos/Plot__should_allow_max_x_max_plot.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_max_x_max_plot.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_no_toolbar_and_no_title.png b/bokehjs/test/baselines/macos/Plot__should_allow_no_toolbar_and_no_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_no_toolbar_and_no_title.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_above_with_title.png b/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_above_with_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_above_with_title.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_above_without_title.png b/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_above_without_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_above_without_title.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_below_with_title.png b/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_below_with_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_below_with_title.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_below_without_title.png b/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_below_without_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_below_without_title.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_left_with_title.png b/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_left_with_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_left_with_title.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_left_without_title.png b/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_left_without_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_left_without_title.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_right_with_title.png b/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_right_with_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_right_with_title.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_right_without_title.png b/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_right_without_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__should_allow_toolbar_placement_right_without_title.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__with_webgl_backend__should_allow_empty_circle_glyphs.png b/bokehjs/test/baselines/macos/Plot__with_webgl_backend__should_allow_empty_circle_glyphs.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__with_webgl_backend__should_allow_empty_circle_glyphs.png differ
diff --git a/bokehjs/test/baselines/macos/Plot__with_webgl_backend__should_allow_empty_line_glyphs.png b/bokehjs/test/baselines/macos/Plot__with_webgl_backend__should_allow_empty_line_glyphs.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Plot__with_webgl_backend__should_allow_empty_line_glyphs.png differ
diff --git a/bokehjs/test/baselines/macos/Row__should_allow_to_expand_when_child_policy_is_max.png b/bokehjs/test/baselines/macos/Row__should_allow_to_expand_when_child_policy_is_max.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Row__should_allow_to_expand_when_child_policy_is_max.png differ
diff --git a/bokehjs/test/baselines/macos/Rows_of_widgets__should_allow_different_content_and_fixed_height.png b/bokehjs/test/baselines/macos/Rows_of_widgets__should_allow_different_content_and_fixed_height.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Rows_of_widgets__should_allow_different_content_and_fixed_height.png differ
diff --git a/bokehjs/test/baselines/macos/Tabs__should_allow_tabs_header_location_above.png b/bokehjs/test/baselines/macos/Tabs__should_allow_tabs_header_location_above.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Tabs__should_allow_tabs_header_location_above.png differ
diff --git a/bokehjs/test/baselines/macos/Tabs__should_allow_tabs_header_location_below.png b/bokehjs/test/baselines/macos/Tabs__should_allow_tabs_header_location_below.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Tabs__should_allow_tabs_header_location_below.png differ
diff --git a/bokehjs/test/baselines/macos/Tabs__should_allow_tabs_header_location_left.png b/bokehjs/test/baselines/macos/Tabs__should_allow_tabs_header_location_left.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Tabs__should_allow_tabs_header_location_left.png differ
diff --git a/bokehjs/test/baselines/macos/Tabs__should_allow_tabs_header_location_right.png b/bokehjs/test/baselines/macos/Tabs__should_allow_tabs_header_location_right.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Tabs__should_allow_tabs_header_location_right.png differ
diff --git a/bokehjs/test/baselines/macos/ToolbarBox__should_allow_placement_above_a_figure.png b/bokehjs/test/baselines/macos/ToolbarBox__should_allow_placement_above_a_figure.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/ToolbarBox__should_allow_placement_above_a_figure.png differ
diff --git a/bokehjs/test/baselines/macos/ToolbarBox__should_allow_placement_below_a_figure.png b/bokehjs/test/baselines/macos/ToolbarBox__should_allow_placement_below_a_figure.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/ToolbarBox__should_allow_placement_below_a_figure.png differ
diff --git a/bokehjs/test/baselines/macos/ToolbarBox__should_allow_placement_left_of_a_figure.png b/bokehjs/test/baselines/macos/ToolbarBox__should_allow_placement_left_of_a_figure.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/ToolbarBox__should_allow_placement_left_of_a_figure.png differ
diff --git a/bokehjs/test/baselines/macos/ToolbarBox__should_allow_placement_right_of_a_figure.png b/bokehjs/test/baselines/macos/ToolbarBox__should_allow_placement_right_of_a_figure.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/ToolbarBox__should_allow_placement_right_of_a_figure.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_AutocompleteInput.png b/bokehjs/test/baselines/macos/Widgets__should_allow_AutocompleteInput.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_AutocompleteInput.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_Button.png b/bokehjs/test/baselines/macos/Widgets__should_allow_Button.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_Button.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_CheckboxButtonGroup.png b/bokehjs/test/baselines/macos/Widgets__should_allow_CheckboxButtonGroup.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_CheckboxButtonGroup.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_CheckboxGroup.png b/bokehjs/test/baselines/macos/Widgets__should_allow_CheckboxGroup.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_CheckboxGroup.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_DatePicker.png b/bokehjs/test/baselines/macos/Widgets__should_allow_DatePicker.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_DatePicker.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_DateRangeSlider.png b/bokehjs/test/baselines/macos/Widgets__should_allow_DateRangeSlider.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_DateRangeSlider.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_DateSlider.png b/bokehjs/test/baselines/macos/Widgets__should_allow_DateSlider.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_DateSlider.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_Div.png b/bokehjs/test/baselines/macos/Widgets__should_allow_Div.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_Div.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_Dropdown.png b/bokehjs/test/baselines/macos/Widgets__should_allow_Dropdown.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_Dropdown.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_MultiSelect.png b/bokehjs/test/baselines/macos/Widgets__should_allow_MultiSelect.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_MultiSelect.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_Paragraph.png b/bokehjs/test/baselines/macos/Widgets__should_allow_Paragraph.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_Paragraph.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_PreText.png b/bokehjs/test/baselines/macos/Widgets__should_allow_PreText.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_PreText.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_RadioButtonGroup.png b/bokehjs/test/baselines/macos/Widgets__should_allow_RadioButtonGroup.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_RadioButtonGroup.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_RadioGroup.png b/bokehjs/test/baselines/macos/Widgets__should_allow_RadioGroup.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_RadioGroup.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_RangeSlider.png b/bokehjs/test/baselines/macos/Widgets__should_allow_RangeSlider.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_RangeSlider.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_Select.png b/bokehjs/test/baselines/macos/Widgets__should_allow_Select.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_Select.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_Slider.png b/bokehjs/test/baselines/macos/Widgets__should_allow_Slider.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_Slider.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_TextInput.png b/bokehjs/test/baselines/macos/Widgets__should_allow_TextInput.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_TextInput.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_Toggle.png b/bokehjs/test/baselines/macos/Widgets__should_allow_Toggle.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_Toggle.png differ
diff --git a/bokehjs/test/baselines/macos/Widgets__should_allow_split_Dropdown.png b/bokehjs/test/baselines/macos/Widgets__should_allow_split_Dropdown.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/Widgets__should_allow_split_Dropdown.png differ
diff --git a/bokehjs/test/baselines/macos/gridplot()__should_align_axes_when_toolbar_location=above.png b/bokehjs/test/baselines/macos/gridplot()__should_align_axes_when_toolbar_location=above.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/gridplot()__should_align_axes_when_toolbar_location=above.png differ
diff --git a/bokehjs/test/baselines/macos/gridplot()__should_align_axes_when_toolbar_location=below.png b/bokehjs/test/baselines/macos/gridplot()__should_align_axes_when_toolbar_location=below.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/gridplot()__should_align_axes_when_toolbar_location=below.png differ
diff --git a/bokehjs/test/baselines/macos/gridplot()__should_align_axes_when_toolbar_location=left.png b/bokehjs/test/baselines/macos/gridplot()__should_align_axes_when_toolbar_location=left.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/gridplot()__should_align_axes_when_toolbar_location=left.png differ
diff --git a/bokehjs/test/baselines/macos/gridplot()__should_align_axes_when_toolbar_location=null.png b/bokehjs/test/baselines/macos/gridplot()__should_align_axes_when_toolbar_location=null.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/gridplot()__should_align_axes_when_toolbar_location=null.png differ
diff --git a/bokehjs/test/baselines/macos/gridplot()__should_align_axes_when_toolbar_location=right.png b/bokehjs/test/baselines/macos/gridplot()__should_align_axes_when_toolbar_location=right.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/macos/gridplot()__should_align_axes_when_toolbar_location=right.png differ
diff --git a/bokehjs/test/baselines/windows/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=center.png b/bokehjs/test/baselines/windows/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=center.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=center.png differ
diff --git a/bokehjs/test/baselines/windows/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=end.png b/bokehjs/test/baselines/windows/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=end.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=end.png differ
diff --git a/bokehjs/test/baselines/windows/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=start.png b/bokehjs/test/baselines/windows/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=start.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/3x3_GridBox__fixed,_inconsistent_width_height,_row_col_auto_align=start.png differ
diff --git a/bokehjs/test/baselines/windows/3x3_GridBox__fixed_and_1_x-max_spacers,_c2_auto.png b/bokehjs/test/baselines/windows/3x3_GridBox__fixed_and_1_x-max_spacers,_c2_auto.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/3x3_GridBox__fixed_and_1_x-max_spacers,_c2_auto.png differ
diff --git a/bokehjs/test/baselines/windows/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_auto.png b/bokehjs/test/baselines/windows/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_auto.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_auto.png differ
diff --git a/bokehjs/test/baselines/windows/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_flex=2.png b/bokehjs/test/baselines/windows/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_flex=2.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/3x3_GridBox__fixed_and_2_x-max_spacers,_c2_flex=2.png differ
diff --git a/bokehjs/test/baselines/windows/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2.png b/bokehjs/test/baselines/windows/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2.png differ
diff --git a/bokehjs/test/baselines/windows/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2_align=end.png b/bokehjs/test/baselines/windows/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2_align=end.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/3x3_GridBox__fixed_and_3_x-max_spacers,_c2_flex=2_align=end.png differ
diff --git a/bokehjs/test/baselines/windows/3x3_GridBox__fixed_spacers_50px_x_50px,_spacing_5px.png b/bokehjs/test/baselines/windows/3x3_GridBox__fixed_spacers_50px_x_50px,_spacing_5px.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/3x3_GridBox__fixed_spacers_50px_x_50px,_spacing_5px.png differ
diff --git a/bokehjs/test/baselines/windows/3x3_GridBox__fixed_spacers_50px_x_50px,_vspacing_5px,_hspacing_10px.png b/bokehjs/test/baselines/windows/3x3_GridBox__fixed_spacers_50px_x_50px,_vspacing_5px,_hspacing_10px.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/3x3_GridBox__fixed_spacers_50px_x_50px,_vspacing_5px,_hspacing_10px.png differ
diff --git a/bokehjs/test/baselines/windows/3x3_GridBox__fixed_spacers_50px_x_50px.png b/bokehjs/test/baselines/windows/3x3_GridBox__fixed_spacers_50px_x_50px.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/3x3_GridBox__fixed_spacers_50px_x_50px.png differ
diff --git a/bokehjs/test/baselines/windows/Bug__in_issue_#9522__disallows_arrow_to_be_positioned_correctly_in_stacked_layouts.png b/bokehjs/test/baselines/windows/Bug__in_issue_#9522__disallows_arrow_to_be_positioned_correctly_in_stacked_layouts.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Bug__in_issue_#9522__disallows_arrow_to_be_positioned_correctly_in_stacked_layouts.png differ
diff --git a/bokehjs/test/baselines/windows/Bug__in_issue_#9703__disallows_ImageURL_glyph_to_set_anchor_and_angle_at_the_same_time.png b/bokehjs/test/baselines/windows/Bug__in_issue_#9703__disallows_ImageURL_glyph_to_set_anchor_and_angle_at_the_same_time.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Bug__in_issue_#9703__disallows_ImageURL_glyph_to_set_anchor_and_angle_at_the_same_time.png differ
diff --git a/bokehjs/test/baselines/windows/Bug__in_issue_#9724__makes_automatic_padding_in_data_ranges_inconsistent.png b/bokehjs/test/baselines/windows/Bug__in_issue_#9724__makes_automatic_padding_in_data_ranges_inconsistent.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Bug__in_issue_#9724__makes_automatic_padding_in_data_ranges_inconsistent.png differ
diff --git a/bokehjs/test/baselines/windows/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_fill_color_==_null.png b/bokehjs/test/baselines/windows/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_fill_color_==_null.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_fill_color_==_null.png differ
diff --git a/bokehjs/test/baselines/windows/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_line_color_==_null.png b/bokehjs/test/baselines/windows/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_line_color_==_null.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Bug__in_issue_#9877__disallows_BoxAnnotation_to_respect_line_color_==_null.png differ
diff --git a/bokehjs/test/baselines/windows/Bug__in_issue_#9879__disallows_to_change_FactorRange_to_a_lower_dimension_with_a_different_number_of_factors.png b/bokehjs/test/baselines/windows/Bug__in_issue_#9879__disallows_to_change_FactorRange_to_a_lower_dimension_with_a_different_number_of_factors.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Bug__in_issue_#9879__disallows_to_change_FactorRange_to_a_lower_dimension_with_a_different_number_of_factors.png differ
diff --git a/bokehjs/test/baselines/windows/GridBox__should_allow_20x20_grids_of_25x25_px_spacers.png b/bokehjs/test/baselines/windows/GridBox__should_allow_20x20_grids_of_25x25_px_spacers.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/GridBox__should_allow_20x20_grids_of_25x25_px_spacers.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout.png b/bokehjs/test/baselines/windows/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout_with_a_title.png b/bokehjs/test/baselines/windows/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout_with_a_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_fixed_frame_200px_x_200px_in_a_layout_with_a_title.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_fixed_frame_300px_x_300px.png b/bokehjs/test/baselines/windows/Plot__should_allow_fixed_frame_300px_x_300px.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_fixed_frame_300px_x_300px.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_fixed_x_fixed_plot.png b/bokehjs/test/baselines/windows/Plot__should_allow_fixed_x_fixed_plot.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_fixed_x_fixed_plot.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_fixed_x_max_plot,_aspect_0.8.png b/bokehjs/test/baselines/windows/Plot__should_allow_fixed_x_max_plot,_aspect_0.8.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_fixed_x_max_plot,_aspect_0.8.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_fixed_x_max_plot,_aspect_1.0.png b/bokehjs/test/baselines/windows/Plot__should_allow_fixed_x_max_plot,_aspect_1.0.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_fixed_x_max_plot,_aspect_1.0.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_fixed_x_max_plot,_aspect_1.25.png b/bokehjs/test/baselines/windows/Plot__should_allow_fixed_x_max_plot,_aspect_1.25.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_fixed_x_max_plot,_aspect_1.25.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_fixed_x_max_plot.png b/bokehjs/test/baselines/windows/Plot__should_allow_fixed_x_max_plot.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_fixed_x_max_plot.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_max_x_fixed_plot,_aspect_0.8.png b/bokehjs/test/baselines/windows/Plot__should_allow_max_x_fixed_plot,_aspect_0.8.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_max_x_fixed_plot,_aspect_0.8.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_max_x_fixed_plot,_aspect_1.0.png b/bokehjs/test/baselines/windows/Plot__should_allow_max_x_fixed_plot,_aspect_1.0.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_max_x_fixed_plot,_aspect_1.0.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_max_x_fixed_plot,_aspect_1.25.png b/bokehjs/test/baselines/windows/Plot__should_allow_max_x_fixed_plot,_aspect_1.25.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_max_x_fixed_plot,_aspect_1.25.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_max_x_fixed_plot.png b/bokehjs/test/baselines/windows/Plot__should_allow_max_x_fixed_plot.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_max_x_fixed_plot.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_max_x_max_plot,_aspect_0.8.png b/bokehjs/test/baselines/windows/Plot__should_allow_max_x_max_plot,_aspect_0.8.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_max_x_max_plot,_aspect_0.8.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_max_x_max_plot,_aspect_1.0.png b/bokehjs/test/baselines/windows/Plot__should_allow_max_x_max_plot,_aspect_1.0.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_max_x_max_plot,_aspect_1.0.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_max_x_max_plot,_aspect_1.25.png b/bokehjs/test/baselines/windows/Plot__should_allow_max_x_max_plot,_aspect_1.25.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_max_x_max_plot,_aspect_1.25.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_max_x_max_plot.png b/bokehjs/test/baselines/windows/Plot__should_allow_max_x_max_plot.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_max_x_max_plot.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_no_toolbar_and_no_title.png b/bokehjs/test/baselines/windows/Plot__should_allow_no_toolbar_and_no_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_no_toolbar_and_no_title.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_above_with_title.png b/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_above_with_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_above_with_title.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_above_without_title.png b/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_above_without_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_above_without_title.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_below_with_title.png b/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_below_with_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_below_with_title.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_below_without_title.png b/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_below_without_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_below_without_title.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_left_with_title.png b/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_left_with_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_left_with_title.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_left_without_title.png b/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_left_without_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_left_without_title.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_right_with_title.png b/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_right_with_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_right_with_title.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_right_without_title.png b/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_right_without_title.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__should_allow_toolbar_placement_right_without_title.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__with_webgl_backend__should_allow_empty_circle_glyphs.png b/bokehjs/test/baselines/windows/Plot__with_webgl_backend__should_allow_empty_circle_glyphs.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__with_webgl_backend__should_allow_empty_circle_glyphs.png differ
diff --git a/bokehjs/test/baselines/windows/Plot__with_webgl_backend__should_allow_empty_line_glyphs.png b/bokehjs/test/baselines/windows/Plot__with_webgl_backend__should_allow_empty_line_glyphs.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Plot__with_webgl_backend__should_allow_empty_line_glyphs.png differ
diff --git a/bokehjs/test/baselines/windows/Row__should_allow_to_expand_when_child_policy_is_max.png b/bokehjs/test/baselines/windows/Row__should_allow_to_expand_when_child_policy_is_max.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Row__should_allow_to_expand_when_child_policy_is_max.png differ
diff --git a/bokehjs/test/baselines/windows/Rows_of_widgets__should_allow_different_content_and_fixed_height.png b/bokehjs/test/baselines/windows/Rows_of_widgets__should_allow_different_content_and_fixed_height.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Rows_of_widgets__should_allow_different_content_and_fixed_height.png differ
diff --git a/bokehjs/test/baselines/windows/Tabs__should_allow_tabs_header_location_above.png b/bokehjs/test/baselines/windows/Tabs__should_allow_tabs_header_location_above.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Tabs__should_allow_tabs_header_location_above.png differ
diff --git a/bokehjs/test/baselines/windows/Tabs__should_allow_tabs_header_location_below.png b/bokehjs/test/baselines/windows/Tabs__should_allow_tabs_header_location_below.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Tabs__should_allow_tabs_header_location_below.png differ
diff --git a/bokehjs/test/baselines/windows/Tabs__should_allow_tabs_header_location_left.png b/bokehjs/test/baselines/windows/Tabs__should_allow_tabs_header_location_left.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Tabs__should_allow_tabs_header_location_left.png differ
diff --git a/bokehjs/test/baselines/windows/Tabs__should_allow_tabs_header_location_right.png b/bokehjs/test/baselines/windows/Tabs__should_allow_tabs_header_location_right.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Tabs__should_allow_tabs_header_location_right.png differ
diff --git a/bokehjs/test/baselines/windows/ToolbarBox__should_allow_placement_above_a_figure.png b/bokehjs/test/baselines/windows/ToolbarBox__should_allow_placement_above_a_figure.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/ToolbarBox__should_allow_placement_above_a_figure.png differ
diff --git a/bokehjs/test/baselines/windows/ToolbarBox__should_allow_placement_below_a_figure.png b/bokehjs/test/baselines/windows/ToolbarBox__should_allow_placement_below_a_figure.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/ToolbarBox__should_allow_placement_below_a_figure.png differ
diff --git a/bokehjs/test/baselines/windows/ToolbarBox__should_allow_placement_left_of_a_figure.png b/bokehjs/test/baselines/windows/ToolbarBox__should_allow_placement_left_of_a_figure.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/ToolbarBox__should_allow_placement_left_of_a_figure.png differ
diff --git a/bokehjs/test/baselines/windows/ToolbarBox__should_allow_placement_right_of_a_figure.png b/bokehjs/test/baselines/windows/ToolbarBox__should_allow_placement_right_of_a_figure.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/ToolbarBox__should_allow_placement_right_of_a_figure.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_AutocompleteInput.png b/bokehjs/test/baselines/windows/Widgets__should_allow_AutocompleteInput.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_AutocompleteInput.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_Button.png b/bokehjs/test/baselines/windows/Widgets__should_allow_Button.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_Button.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_CheckboxButtonGroup.png b/bokehjs/test/baselines/windows/Widgets__should_allow_CheckboxButtonGroup.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_CheckboxButtonGroup.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_CheckboxGroup.png b/bokehjs/test/baselines/windows/Widgets__should_allow_CheckboxGroup.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_CheckboxGroup.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_DatePicker.png b/bokehjs/test/baselines/windows/Widgets__should_allow_DatePicker.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_DatePicker.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_DateRangeSlider.png b/bokehjs/test/baselines/windows/Widgets__should_allow_DateRangeSlider.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_DateRangeSlider.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_DateSlider.png b/bokehjs/test/baselines/windows/Widgets__should_allow_DateSlider.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_DateSlider.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_Div.png b/bokehjs/test/baselines/windows/Widgets__should_allow_Div.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_Div.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_Dropdown.png b/bokehjs/test/baselines/windows/Widgets__should_allow_Dropdown.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_Dropdown.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_MultiSelect.png b/bokehjs/test/baselines/windows/Widgets__should_allow_MultiSelect.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_MultiSelect.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_Paragraph.png b/bokehjs/test/baselines/windows/Widgets__should_allow_Paragraph.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_Paragraph.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_PreText.png b/bokehjs/test/baselines/windows/Widgets__should_allow_PreText.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_PreText.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_RadioButtonGroup.png b/bokehjs/test/baselines/windows/Widgets__should_allow_RadioButtonGroup.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_RadioButtonGroup.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_RadioGroup.png b/bokehjs/test/baselines/windows/Widgets__should_allow_RadioGroup.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_RadioGroup.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_RangeSlider.png b/bokehjs/test/baselines/windows/Widgets__should_allow_RangeSlider.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_RangeSlider.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_Select.png b/bokehjs/test/baselines/windows/Widgets__should_allow_Select.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_Select.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_Slider.png b/bokehjs/test/baselines/windows/Widgets__should_allow_Slider.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_Slider.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_TextInput.png b/bokehjs/test/baselines/windows/Widgets__should_allow_TextInput.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_TextInput.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_Toggle.png b/bokehjs/test/baselines/windows/Widgets__should_allow_Toggle.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_Toggle.png differ
diff --git a/bokehjs/test/baselines/windows/Widgets__should_allow_split_Dropdown.png b/bokehjs/test/baselines/windows/Widgets__should_allow_split_Dropdown.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/Widgets__should_allow_split_Dropdown.png differ
diff --git a/bokehjs/test/baselines/windows/gridplot()__should_align_axes_when_toolbar_location=above.png b/bokehjs/test/baselines/windows/gridplot()__should_align_axes_when_toolbar_location=above.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/gridplot()__should_align_axes_when_toolbar_location=above.png differ
diff --git a/bokehjs/test/baselines/windows/gridplot()__should_align_axes_when_toolbar_location=below.png b/bokehjs/test/baselines/windows/gridplot()__should_align_axes_when_toolbar_location=below.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/gridplot()__should_align_axes_when_toolbar_location=below.png differ
diff --git a/bokehjs/test/baselines/windows/gridplot()__should_align_axes_when_toolbar_location=left.png b/bokehjs/test/baselines/windows/gridplot()__should_align_axes_when_toolbar_location=left.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/gridplot()__should_align_axes_when_toolbar_location=left.png differ
diff --git a/bokehjs/test/baselines/windows/gridplot()__should_align_axes_when_toolbar_location=null.png b/bokehjs/test/baselines/windows/gridplot()__should_align_axes_when_toolbar_location=null.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/gridplot()__should_align_axes_when_toolbar_location=null.png differ
diff --git a/bokehjs/test/baselines/windows/gridplot()__should_align_axes_when_toolbar_location=right.png b/bokehjs/test/baselines/windows/gridplot()__should_align_axes_when_toolbar_location=right.png
new file mode 100644
Binary files /dev/null and b/bokehjs/test/baselines/windows/gridplot()__should_align_axes_when_toolbar_location=right.png differ
diff --git a/bokehjs/test/unit/defaults.ts b/bokehjs/test/defaults/index.ts
similarity index 80%
rename from bokehjs/test/unit/defaults.ts
rename to bokehjs/test/defaults/index.ts
--- a/bokehjs/test/unit/defaults.ts
+++ b/bokehjs/test/defaults/index.ts
@@ -1,35 +1,37 @@
+import {describe, it} from "../framework"
+export * from "../framework"
+
 import {expect} from "chai"
 
-import models_defaults from "../.generated_defaults/models_defaults.json"
-import widget_defaults from "../.generated_defaults/widgets_defaults.json"
+import all_defaults from "../.generated_defaults/defaults.json"
 
 import {isArray, isPlainObject} from "@bokehjs/core/util/types"
-import {difference, concat} from "@bokehjs/core/util/array"
+import {difference} from "@bokehjs/core/util/array"
 import {keys} from "@bokehjs/core/util/object"
 import {isEqual} from "@bokehjs/core/util/eq"
 
 import {Models} from "@bokehjs/base"
 import {HasProps} from "@bokehjs/core/has_props"
 
-import {Widgets as widget_models} from "@bokehjs/models/widgets/main"
-import {Tables as table_models} from "@bokehjs/models/widgets/tables/main"
+import "@bokehjs/models/widgets/main"
+import "@bokehjs/models/widgets/tables/main"
 
 function get_defaults(name: string) {
-  const defaults = models_defaults[name] || widget_defaults[name]
+  const defaults = all_defaults[name]
   if (defaults != null)
     return defaults
   else
     throw new Error(`can't find defaults for ${name}`)
 }
 
-function safe_stringify(v: any): string {
+function safe_stringify(v: unknown): string {
   if (v === Infinity) {
     return "Infinity"
   } else {
     try {
       return JSON.stringify(v)
-    } catch (_e) {
-      return v.toString()
+    } catch {
+      return `${v}`
     }
   }
 }
@@ -181,33 +183,6 @@ function strip_ids(value: any): void {
 }
 
 describe("Defaults", () => {
-  // this is skipped while we decide whether to automate putting them all
-  // in Bokeh or just leave it as a curated (or ad hoc?) subset
-  it.skip("have all non-Widget view models from Python in the Models object", () => {
-    const missing = []
-    for (const name of keys(models_defaults)) {
-      if (!(name in Models.registered_names())) {
-        missing.push(name)
-      }
-    }
-    for (const m of missing) {
-      console.error(`'Models.${m}' not found but there's a Python model '${m}'`)
-    }
-    expect(missing.length).to.equal(0)
-  })
-
-  it("have all Widget view models from Python in widget locations registry", () => {
-    const missing = []
-    for (const name of keys(widget_defaults)) {
-      if (!(name in widget_models || name in table_models)) {
-        missing.push(name)
-      }
-    }
-    for (const m of missing) {
-      console.error(`'${m}' not found but there's a Python model '${m}'`)
-    }
-    expect(missing.length).to.equal(0)
-  })
 
   it("have all view models from Python in registered locations", () => {
     const registered: {[key: string]: boolean} = {}
@@ -215,7 +190,7 @@ describe("Defaults", () => {
       registered[name] = true
     }
     const missing = []
-    const all_view_model_names = concat([keys(models_defaults), keys(widget_defaults)])
+    const all_view_model_names = keys(all_defaults)
     for (const name of all_view_model_names) {
       if (!(name in registered)) {
         missing.push(name)
@@ -229,7 +204,7 @@ describe("Defaults", () => {
 
   it("match between Python and bokehjs", () => {
     let fail_count = 0
-    const all_view_model_names = concat([keys(models_defaults), keys(widget_defaults)])
+    const all_view_model_names = keys(all_defaults)
     for (const name of all_view_model_names) {
       const model = Models(name)
       const attrs: {[key: string]: unknown} = {}
diff --git a/bokehjs/test/json.d.ts b/bokehjs/test/defaults/json.d.ts
similarity index 100%
rename from bokehjs/test/json.d.ts
rename to bokehjs/test/defaults/json.d.ts
diff --git a/bokehjs/test/tsconfig.json b/bokehjs/test/defaults/tsconfig.json
similarity index 83%
rename from bokehjs/test/tsconfig.json
rename to bokehjs/test/defaults/tsconfig.json
--- a/bokehjs/test/tsconfig.json
+++ b/bokehjs/test/defaults/tsconfig.json
@@ -26,13 +26,13 @@
     "types": [],
     "baseUrl": ".",
     "paths": {
-      "@bokehjs/*": ["../build/js/lib/*", "../build/js/types/*"]
+      "@bokehjs/*": ["../../build/js/lib/*", "../../build/js/types/*"]
     },
-    "outDir": "../build/test"
+    "outDir": "../../build/test"
   },
   "include": [
-    "./*.ts",
-    "./unit/**/*.ts",
-    "./integration/**/*.ts"
+    "../framework.ts",
+    "../*.d.ts",
+    "./**/*.ts"
   ]
 }
diff --git a/bokehjs/test/devtools/devtools.ts b/bokehjs/test/devtools/devtools.ts
--- a/bokehjs/test/devtools/devtools.ts
+++ b/bokehjs/test/devtools/devtools.ts
@@ -9,6 +9,7 @@ import {Bar, Presets} from "cli-progress"
 
 import {Box, State, create_baseline, load_baseline, diff_baseline, load_baseline_image} from "./baselines"
 import {diff_image} from "./image"
+import {platform} from "./sys"
 
 const url = argv._[0]
 const port = parseInt(argv.port as string | undefined ?? "9222")
@@ -52,7 +53,7 @@ function encode(s: string): string {
 }
 
 type Suite = {description: string, suites: Suite[], tests: Test[]}
-type Test = {description: string, skip: boolean}
+type Test = {description: string, skip: boolean, threshold?: number}
 
 type Result = {error: {str: string, stack?: string} | null, time: number, state?: State, bbox?: Box}
 
@@ -63,7 +64,7 @@ async function run_tests(): Promise<void> {
   let handle_exceptions = true
   try {
     client = await CDP({port})
-    const {Network, Page, Runtime, Log} = client
+    const {Emulation, Network, Page, Runtime, Log} = client
     try {
       function collect_trace(stackTrace: Protocol.Runtime.StackTrace): CallFrame[] {
         return stackTrace.callFrames.map(({functionName, url, lineNumber, columnNumber}) => {
@@ -160,6 +161,13 @@ async function run_tests(): Promise<void> {
       await Page.enable()
       await Log.enable()
 
+      await Emulation.setDeviceMetricsOverride({
+        width: 1000,
+        height: 1000,
+        deviceScaleFactor: 1,
+        mobile: false,
+      })
+
       const {errorText} = await Page.navigate({url})
 
       if (errorText != null) {
@@ -260,8 +268,6 @@ async function run_tests(): Promise<void> {
       }, Presets.shades_classic)
 
       const baselines_root = (argv.baselinesRoot as string | undefined) ?? null
-
-      path.join("test", "baselines")
       const baseline_names = new Set<string>()
 
       let skipped = 0
@@ -313,91 +319,115 @@ async function run_tests(): Promise<void> {
           if (test.skip) {
             status.skipped = true
           } else {
-            const seq = JSON.stringify(to_seq(suites, test))
-            const output = await evaluate<Result>(`Tests.run(${seq})`)
-            try {
-              const errors = entries.filter((entry) => entry.level == "error")
-              if (errors.length != 0) {
-                status.errors.push(...errors.map((entry) => entry.text))
-                // status.failure = true // XXX: too chatty right now
-              }
+            async function run_test(i: number | null, status: Status): Promise<boolean> {
+              let may_retry = false
+              const seq = JSON.stringify(to_seq(suites, test))
+              const output = await evaluate<Result>(`Tests.run(${seq})`)
+              try {
+                const errors = entries.filter((entry) => entry.level == "error")
+                if (errors.length != 0) {
+                  status.errors.push(...errors.map((entry) => entry.text))
+                  // status.failure = true // XXX: too chatty right now
+                }
 
-              if (output instanceof Failure) {
-                status.errors.push(output.text)
-                status.failure = true
-              } else if (output instanceof Timeout) {
-                status.errors.push("timeout")
-                status.timeout = true
-              } else {
-                const result = output.value
-                if (result.error != null) {
-                  const {str, stack} = result.error
-                  status.errors.push(stack ?? str)
+                if (output instanceof Failure) {
+                  status.errors.push(output.text)
                   status.failure = true
-                } else if (baselines_root != null) {
-                  if (result.state == null) {
-                    status.errors.push("state not present in output")
+                } else if (output instanceof Timeout) {
+                  status.errors.push("timeout")
+                  status.timeout = true
+                } else {
+                  const result = output.value
+                  if (result.error != null) {
+                    const {str, stack} = result.error
+                    status.errors.push(stack ?? str)
                     status.failure = true
-                  } else {
-                    const baseline_path = path.join(baselines_root, baseline_name)
-
-                    const {bbox} = result
-                    if (bbox != null) {
-                      const image = await Page.captureScreenshot({format: "png", clip: {...bbox, scale: 1}})
-                      const current = Buffer.from(image.data, "base64")
-                      status.image = current
-
-                      const image_path = `${baseline_path}.png`
-                      const write_image = async () => fs.promises.writeFile(image_path, current)
-                      const existing = load_baseline_image(image_path)
-
-                      switch (argv.screenshot) {
-                        case undefined:
-                        case "test":
+                  } else if (baselines_root != null) {
+                    const {state} = result
+                    if (state == null) {
+                      status.errors.push("state not present in output")
+                      status.failure = true
+                    } else {
+                      await (async () => {
+                        const baseline_path = path.join(baselines_root, baseline_name)
+
+                        const baseline = create_baseline([state])
+                        await fs.promises.writeFile(baseline_path, baseline)
+                        status.baseline = baseline
+
+                        const existing = load_baseline(baseline_path)
+                        if (existing != baseline) {
                           if (existing == null) {
-                            status.failure = true
-                            status.errors.push("missing baseline image")
-                            await write_image()
-                          } else {
-                            status.reference = existing
-                            const result = diff_image(existing, current)
-                            if (result != null) {
+                            status.errors.push("missing baseline")
+                          }
+                          const diff = diff_baseline(baseline_path)
+                          status.failure = true
+                          status.baseline_diff = diff
+                          status.errors.push(diff)
+                        }
+                      })()
+
+                      await (async () => {
+                        const baseline_path = path.join(baselines_root, platform, baseline_name)
+
+                        const {bbox} = result
+                        if (bbox != null) {
+                          const image = await Page.captureScreenshot({format: "png", clip: {...bbox, scale: 1}})
+                          const current = Buffer.from(image.data, "base64")
+                          status.image = current
+
+                          const image_path = `${baseline_path}.png`
+                          const write_image = async () => fs.promises.writeFile(image_path, current)
+                          const existing = load_baseline_image(image_path)
+
+                          switch (argv.screenshot) {
+                            case undefined:
+                            case "test":
+                              if (existing == null) {
+                                status.failure = true
+                                status.errors.push("missing baseline image")
+                                await write_image()
+                              } else {
+                                status.reference = existing
+                                const diff_result = diff_image(existing, current)
+                                if (diff_result != null) {
+                                  may_retry = true
+                                  const {diff, pixels, percent} = diff_result
+                                  const threshold = test.threshold ?? 0
+                                  if (pixels > threshold) {
+                                    await write_image()
+                                    status.failure = true
+                                    status.image_diff = diff
+                                    status.errors.push(`images differ by ${pixels}px (${percent.toFixed(2)}%)${i != null ? ` (i=${i})` : ""}`)
+                                  }
+                                }
+                              }
+                              break
+                            case "save":
                               await write_image()
-                              status.failure = true
-                              status.image_diff = result.diff
-                              status.errors.push(`images differ by ${result.pixels}px (${result.percent.toFixed(2)}%)`)
-                            }
+                              break
+                            case "skip":
+                              break
+                            default:
+                              throw new Error(`invalid argument --screenshot=${argv.screenshot}`)
                           }
-                          break
-                        case "save":
-                          await write_image()
-                          break
-                        case "skip":
-                          break
-                        default:
-                          throw new Error(`invalid argument --screenshot=${argv.screenshot}`)
-                      }
-                    }
-
-                    const baseline = create_baseline([result.state])
-                    await fs.promises.writeFile(baseline_path, baseline)
-                    status.baseline = baseline
-
-                    const existing = load_baseline(baseline_path)
-                    if (existing != baseline) {
-                      if (existing == null) {
-                        status.errors.push("missing baseline")
-                      }
-                      const diff = diff_baseline(baseline_path)
-                      status.failure = true
-                      status.baseline_diff = diff
-                      status.errors.push(diff)
+                        }
+                      })()
                     }
                   }
                 }
+              } finally {
+                await evaluate(`Tests.clear(${seq})`)
+              }
+
+              return may_retry
+            }
+
+            const retry = await run_test(null, status)
+            if (retry) {
+              for (let i = 0; i < 10; i++) {
+                await run_test(i, status)
               }
-            } finally {
-              await evaluate(`Tests.clear(${seq})`)
             }
           }
 
@@ -429,23 +459,26 @@ async function run_tests(): Promise<void> {
         }
       }
 
-      const json = JSON.stringify(test_suite.map(([suites, test, status]) => {
-        const {failure, image, image_diff, reference} = status
-        return [descriptions(suites, test), {failure, image, image_diff, reference}]
-      }), (_key, value) => {
-        if (value?.type == "Buffer")
-          return Buffer.from(value.data).toString("base64")
-        else
-          return value
-      })
-      await fs.promises.writeFile(path.join("test", "report.json"), json)
+      if (baselines_root != null) {
+        const json = JSON.stringify(test_suite.map(([suites, test, status]) => {
+          const {failure, image, image_diff, reference} = status
+          return [descriptions(suites, test), {failure, image, image_diff, reference}]
+        }), (_key, value) => {
+          if (value?.type == "Buffer")
+            return Buffer.from(value.data).toString("base64")
+          else
+            return value
+        })
+        await fs.promises.writeFile(path.join(baselines_root, platform, "report.json"), json)
 
-      if (baselines_root != null && baseline_names.size != 0) {
         const files = new Set(await fs.promises.readdir(baselines_root))
 
+        files.delete("linux")
+        files.delete("macos")
+        files.delete("windows")
+
         for (const name of baseline_names) {
           files.delete(name)
-          files.delete(`${name}.png`)
         }
 
         if (files.size != 0) {
diff --git a/bokehjs/test/devtools/image.ts b/bokehjs/test/devtools/image.ts
--- a/bokehjs/test/devtools/image.ts
+++ b/bokehjs/test/devtools/image.ts
@@ -77,14 +77,17 @@ export function diff_image(existing: Buffer, current: Buffer, verbose: boolean =
       if (!(h0 == h1 && s0 == s1 && l0 == l1 && _a0 == _a1)) {
         const d = (a: number, b: number) => Math.abs(a - b)
 
+        const hd = d(h0, h1)
+        const sd = d(s0, s1)
+        const ld = d(l0, l1)
 
-        if (!(h0 == h1 && s0 == s1 && /*l0 == l1*/ d(l0, l1) <= 5 && _a0 == _a1)) {
+        if (!(hd <= 0) || !(sd <= 0) || !(ld <= 0)) {
           if (verbose) {
             const [x, y] = [i % width, Math.floor(i / width)]
             console.log("")
             console.log(`existing(${x}, ${y}) = RGBA(${r0}, ${g0}, ${b0}, ${a0}) HSLA(${h0}, ${s0}, ${l0}, ${_a0})`)
             console.log(`current(${x}, ${y})  = RGBA(${r1}, ${g1}, ${b1}, ${a1}) HSLA(${h1}, ${s1}, ${l1}, ${_a1})`)
-            console.log(`d(h0, h1) = ${d(h0, h1)} d(s0, s1) = ${d(s0, s1)} d(l0, l1) = ${d(l0, l1)}`)
+            console.log(`d(h0, h1) = ${hd} d(s0, s1) = ${sd} d(l0, l1) = ${ld}`)
           }
           pixels++
           c32[i] = encode(0, 0, 255)
diff --git a/bokehjs/test/devtools/server.ts b/bokehjs/test/devtools/server.ts
--- a/bokehjs/test/devtools/server.ts
+++ b/bokehjs/test/devtools/server.ts
@@ -5,6 +5,8 @@ import {argv} from "yargs"
 import express from "express"
 import nunjucks from "nunjucks"
 
+import {platform} from "./sys"
+
 const app = express()
 
 nunjucks.configure(__dirname, {
@@ -19,6 +21,9 @@ app.use("/fonts", express.static("test/fonts/"))
 app.get("/unit", (_req, res) => {
   res.render("template.html", {title: "Unit Tests", main: "unit.js"})
 })
+app.get("/defaults", (_req, res) => {
+  res.render("template.html", {title: "Defaults Tests", main: "defaults.js"})
+})
 app.get("/integration", (_req, res) => {
   res.render("template.html", {title: "Integration Tests", main: "integration.js"})
 })
@@ -26,12 +31,16 @@ app.get("/integration", (_req, res) => {
 app.get("/unit/run", (_req, res) => {
   res.render("template.html", {title: "Unit Tests", main: "unit.js", run: true})
 })
+app.get("/defaults/run", (_req, res) => {
+  res.render("template.html", {title: "Defaults Tests", main: "defaults.js", run: true})
+})
 app.get("/integration/run", (_req, res) => {
   res.render("template.html", {title: "Integration Tests", main: "integration.js", run: true})
 })
 
-app.get("/integration/report", async (_req, res) => {
-  const json = await fs.promises.readFile(join("test", "report.json"), {encoding: "utf-8"})
+app.get("/integration/report", async (req, res) => {
+  const report_path = join("test", "baselines", req.query.platform ?? platform, "report.json")
+  const json = await fs.promises.readFile(report_path, {encoding: "utf-8"})
   res.render("report.html", {title: "Integration Tests Report", tests: JSON.parse(json)})
 })
 
diff --git a/bokehjs/test/devtools/sys.ts b/bokehjs/test/devtools/sys.ts
new file mode 100644
--- /dev/null
+++ b/bokehjs/test/devtools/sys.ts
@@ -0,0 +1,13 @@
+import os = require("os")
+
+export const platform = (() => {
+  const platform = os.platform()
+  switch (platform) {
+    case "linux": return "linux"
+    case "darwin": return "macos"
+    case "win32": return "windows"
+    default:
+      console.warn(`unsupported platform '${platform}', assuming 'linux'`)
+      return "linux"
+  }
+})()
diff --git a/bokehjs/test/devtools/template.html b/bokehjs/test/devtools/template.html
--- a/bokehjs/test/devtools/template.html
+++ b/bokehjs/test/devtools/template.html
@@ -25,7 +25,7 @@
         font-family: Bokeh;
       }
 
-      pre.bk {
+      .bk-root pre.bk {
         font-family: BokehMono;
       }
     </style>
@@ -42,6 +42,13 @@
     </script>
   </head>
   <body>
+    <div style="visibility: hidden;">
+      <!-- Preload fonts. Rmove this when https://github.com/bokeh/bokeh/issues/9448 is fixed. -->
+      <span style="font-family: Bokeh;     font-style: normal; font-weight: normal;">Ab01_</span>
+      <span style="font-family: Bokeh;     font-style: normal; font-weight: bold;"  >Ab01_</span>
+      <span style="font-family: Bokeh;     font-style: italic; font-weight: normal;">Ab01_</span>
+      <span style="font-family: BokehMono; font-style: normal; font-weight: normal;">Ab01_</span>
+    </div>
     <script type="text/javascript" src="/static/test/{{ main }}"></script>
     {%- if run %}
     <script>
diff --git a/bokehjs/test/external.d.ts b/bokehjs/test/external.d.ts
--- a/bokehjs/test/external.d.ts
+++ b/bokehjs/test/external.d.ts
@@ -1 +1 @@
-declare module "path-browserify"
+declare module "path-browserify" {}
diff --git a/bokehjs/test/framework.ts b/bokehjs/test/framework.ts
--- a/bokehjs/test/framework.ts
+++ b/bokehjs/test/framework.ts
@@ -19,6 +19,7 @@ export type Test = Decl & {
   skip: boolean
   view?: LayoutDOMView
   el?: HTMLElement
+  threshold?: number
 }
 
 export type Suite = {
@@ -43,17 +44,37 @@ export function describe(description: string, fn: Func/* | AsyncFunc*/): void {
   }
 }
 
-type _It = {(description: string, fn: AsyncFunc): void} & {skip: Fn, with_server: Fn}
+type ItFn = (description: string, fn: Func | AsyncFunc) => Test
+type _It = ItFn & {
+  skip: Fn
+  with_server: Fn
+  allowing: (threshold: number) => ItFn
+}
+
+function _it(description: string, fn: Func | AsyncFunc, skip: boolean): Test {
+  const test = {description, fn, skip}
+  stack[0].tests.push(test)
+  return test
+}
+
+export function allowing(threshold: number): ItFn {
+  return (description: string, fn: Func | AsyncFunc): Test => {
+    const test = it(description, fn)
+    test.threshold = threshold
+    return test
+  }
+}
 
-export function skip(description: string, fn: Func | AsyncFunc): void {
-  stack[0].tests.push({description, fn, skip: true})
+export function skip(description: string, fn: Func | AsyncFunc): Test {
+  return _it(description, fn, true)
 }
 
-export const it: _It = ((description: string, fn: Func | AsyncFunc): void => {
-  stack[0].tests.push({description, fn, skip: false})
+export const it: _It = ((description: string, fn: Func | AsyncFunc): Test => {
+  return _it(description, fn, false)
 }) as _It
 it.skip = skip as any
 it.with_server = skip as any
+it.allowing = allowing
 
 export function before_each(fn: Func | AsyncFunc): void {
   stack[0].before_each.push({fn})
diff --git a/bokehjs/test/globals.d.ts b/bokehjs/test/globals.d.ts
--- a/bokehjs/test/globals.d.ts
+++ b/bokehjs/test/globals.d.ts
@@ -7,6 +7,7 @@ declare type Decl = (description: string, fn: Func | AsyncFunc) => void
 declare type It = Decl & {
   skip: Decl
   with_server: (description: string, fn: (url: string) => Promise<void>) => void
+  allowing: (threshold: number) => Decl
 }
 
 declare const describe: Decl
diff --git a/bokehjs/test/integration/index.ts b/bokehjs/test/integration/index.ts
--- a/bokehjs/test/integration/index.ts
+++ b/bokehjs/test/integration/index.ts
@@ -525,13 +525,13 @@ describe("Widgets", () => {
     await display(obj, [500, 100])
   })
 
-  it("should allow Dropdown", async () => {
+  it.allowing(31)("should allow Dropdown", async () => {
     const menu = ["Item 1", "Item 2", null, "Item 3"]
     const obj = new Dropdown({label: "Dropdown 1", button_type: "primary", menu})
     await display(obj, [500, 100])
   })
 
-  it("should allow split Dropdown", async () => {
+  it.allowing(33)("should allow split Dropdown", async () => {
     const menu = ["Item 1", "Item 2", null, "Item 3"]
     const obj = new Dropdown({label: "Dropdown 2", button_type: "primary", menu, split: true})
     await display(obj, [500, 100])
@@ -547,33 +547,33 @@ describe("Widgets", () => {
     await display(obj, [500, 100])
   })
 
-  it("should allow CheckboxButtonGroup", async () => {
+  it.allowing(7)("should allow CheckboxButtonGroup", async () => {
     const obj = new CheckboxButtonGroup({labels: ["Option 1", "Option 2", "Option 3"], active: [0, 1]})
     await display(obj, [500, 100])
   })
 
-  it("should allow RadioButtonGroup", async () => {
+  it.allowing(9)("should allow RadioButtonGroup", async () => {
     const obj = new RadioButtonGroup({labels: ["Option 1", "Option 2", "Option 3"], active: 0})
     await display(obj, [500, 100])
   })
 
-  it("should allow TextInput", async () => {
+  it.allowing(8)("should allow TextInput", async () => {
     const obj = new TextInput({placeholder: "Enter value ..."})
     await display(obj, [500, 100])
   })
 
-  it("should allow AutocompleteInput", async () => {
+  it.allowing(8)("should allow AutocompleteInput", async () => {
     const completions = ["aaa", "aab", "aac", "baa", "caa"]
     const obj = new AutocompleteInput({placeholder: "Enter value ...", completions})
     await display(obj, [500, 100])
   })
 
-  it("should allow Select", async () => {
+  it.allowing(8)("should allow Select", async () => {
     const obj = new Select({options: ["Option 1", "Option 2", "Option 3"]})
     await display(obj, [500, 100])
   })
 
-  it("should allow MultiSelect", async () => {
+  it.allowing(8)("should allow MultiSelect", async () => {
     const options = range(16).map((i) => `Option ${i+1}`)
     const obj = new MultiSelect({options, size: 6})
     await display(obj, [500, 150])
@@ -607,7 +607,7 @@ describe("Widgets", () => {
     await display(obj, [500, 100])
   })
 
-  it("should allow DatePicker", async () => {
+  it.allowing(8)("should allow DatePicker", async () => {
     const obj = new DatePicker({value: new Date(Date.UTC(2017, 8, 1)).toDateString()})
     await display(obj, [500, 100])
   })
@@ -630,7 +630,7 @@ describe("Widgets", () => {
 
 describe("Rows of widgets", () => {
 
-  it("should allow different content and fixed height", async () => {
+  it.allowing(7)("should allow different content and fixed height", async () => {
     const w0 = new TextInput({value: "Widget 1"})
     const w1 = new TextInput({value: "Widget 2", height: 50})
     const row = new Row({children: [w0, w1]})
diff --git a/bokehjs/test/integration/tsconfig.json b/bokehjs/test/integration/tsconfig.json
new file mode 100644
--- /dev/null
+++ b/bokehjs/test/integration/tsconfig.json
@@ -0,0 +1,38 @@
+{
+  "compilerOptions": {
+    "noImplicitAny": true,
+    "noImplicitThis": true,
+    "noImplicitReturns": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "strictNullChecks": true,
+    "strictBindCallApply": false,
+    "strictFunctionTypes": false,
+    "strictPropertyInitialization": false,
+    "alwaysStrict": true,
+    "noErrorTruncation": true,
+    "noEmitOnError": false,
+    "declaration": false,
+    "sourceMap": true,
+    "importHelpers": true,
+    "experimentalDecorators": true,
+    "module": "ES2020",
+    "moduleResolution": "node",
+    "resolveJsonModule": true,
+    "esModuleInterop": true,
+    "skipLibCheck": true,
+    "target": "ES2020",
+    "lib": ["es2020", "dom", "dom.iterable"],
+    "types": [],
+    "baseUrl": ".",
+    "paths": {
+      "@bokehjs/*": ["../../build/js/lib/*", "../../build/js/types/*"]
+    },
+    "outDir": "../../build/test"
+  },
+  "include": [
+    "../framework.ts",
+    "../*.d.ts",
+    "./**/*.ts"
+  ]
+}
diff --git a/bokehjs/test/setup.ts b/bokehjs/test/setup.ts
--- a/bokehjs/test/setup.ts
+++ b/bokehjs/test/setup.ts
@@ -4,7 +4,7 @@ global.Buffer = {}
 global.process = {version: "unknown"}
 
 // expose path-browserify as path
-import "path"
+import "./path"
 
 import sourcemaps from "source-map-support"
 sourcemaps.install({environment: "browser"})
diff --git a/bokehjs/test/unit/index.ts b/bokehjs/test/unit/index.ts
--- a/bokehjs/test/unit/index.ts
+++ b/bokehjs/test/unit/index.ts
@@ -2,7 +2,6 @@ export * from "../framework"
 
 import "./client"
 import "./core"
-import "./defaults"
 import "./document"
 import "./embed"
 import "./model"
diff --git a/bokehjs/test/unit/models/annotations/color_bar.ts b/bokehjs/test/unit/models/annotations/color_bar.ts
--- a/bokehjs/test/unit/models/annotations/color_bar.ts
+++ b/bokehjs/test/unit/models/annotations/color_bar.ts
@@ -333,7 +333,7 @@ describe("ColorBar module", () => {
         color_mapper: new LinearColorMapper({low: 0, high: 10, palette: Viridis.Viridis10}),
       }, "right")
 
-      expect(view._get_label_extent()).to.be.closeTo(17.2353, 10**-2)
+      expect(view._get_label_extent()).to.be.closeTo(17.3535, 10**-2)
     })
 
     it("ColorBarView._get_label_extent method (orientation='vertical') and no major_labels", async () => {
@@ -367,7 +367,7 @@ describe("ColorBar module", () => {
       }, "right")
 
       const {width, height} = view.compute_legend_dimensions()
-      expect(width).to.be.closeTo(62.2353, 10**-2)
+      expect(width).to.be.closeTo(62.3535, 10**-2)
       expect(height).to.be.equal(120)
     })
 
diff --git a/bokehjs/test/unit/tsconfig.json b/bokehjs/test/unit/tsconfig.json
new file mode 100644
--- /dev/null
+++ b/bokehjs/test/unit/tsconfig.json
@@ -0,0 +1,38 @@
+{
+  "compilerOptions": {
+    "noImplicitAny": true,
+    "noImplicitThis": true,
+    "noImplicitReturns": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "strictNullChecks": true,
+    "strictBindCallApply": false,
+    "strictFunctionTypes": false,
+    "strictPropertyInitialization": false,
+    "alwaysStrict": true,
+    "noErrorTruncation": true,
+    "noEmitOnError": false,
+    "declaration": false,
+    "sourceMap": true,
+    "importHelpers": true,
+    "experimentalDecorators": true,
+    "module": "ES2020",
+    "moduleResolution": "node",
+    "resolveJsonModule": true,
+    "esModuleInterop": true,
+    "skipLibCheck": true,
+    "target": "ES2020",
+    "lib": ["es2020", "dom", "dom.iterable"],
+    "types": [],
+    "baseUrl": ".",
+    "paths": {
+      "@bokehjs/*": ["../../build/js/lib/*", "../../build/js/types/*"]
+    },
+    "outDir": "../../build/test"
+  },
+  "include": [
+    "../framework.ts",
+    "../*.d.ts",
+    "./**/*.ts"
+  ]
+}