diff --git a/AGENTS.md b/AGENTS.md
index faa048d9..4c6d156e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -35,6 +35,36 @@ The suite boots a real Chrome and a Sinatra/Puma test app (`spec/support/applica
`lib/ferrum/contexts.rb` -> `spec/unit/contexts_spec.rb`, `lib/ferrum/client/web_socket.rb` ->
`spec/unit/client/web_socket_spec.rb`. Never add them to the feature spec files in `spec/`.
- Don't test private methods; cover them through the public API.
+- Structure every spec file in one of two shapes, nothing else:
+
+ ```ruby
+ describe Ferrum::Context do
+ describe "#windows" do
+ it "waits for the window to load" do
+ ```
+
+ ```ruby
+ describe Ferrum::Cookies do
+ describe "#each" do
+ context "when a block is given" do
+ it "yields each cookie" do
+ ```
+
+ The top `describe` is the class, and the file path mirrors that class's path in `lib/`, for feature and unit
+ specs alike: `Ferrum::Page::Screenshot` -> `spec/page/screenshot_spec.rb`, `Ferrum::Browser::Process` ->
+ `spec/unit/browser/process_spec.rb`. Each nested `describe` names one public method (`"#method"`, `".method"` for
+ class methods), never a topic like `"context scope"`.
+- A `describe` holds either only examples or only contexts, never a mix. Use contexts only for contrasting groups,
+ at least two that oppose each other (`"when a block is given"` / `"when no block is given"`). A single condition
+ next to plain examples isn't a context: keep the examples flat and put the condition in the example name, e.g.
+ `it "sets cookies for given domain when base_url isn't set"`. The one exception: a single context is fine when it
+ carries `let`, hooks (`before`/`after`/`around`) or tags that its examples share.
+- Every example must be runnable alone with `rspec spec/file_spec.rb:LINE`, so each one gets its own `it` on its
+ own line. No `shared_examples`/`include_examples` and no loops that generate examples. Share the steps and
+ assertions through a helper method in the `describe` instead, and call it from one-line examples, one per
+ variant. The only exception is the data-table loops in `spec/unit/frame/script_spec.rb`.
+- Start every example name with a verb in the third person: "yields each cookie", "returns the cookie with the
+ given name". Never "must ...", "should ..." or a bare noun.
- Almost no comments in specs; the example name says what is checked.
- For a bug fix, make sure the new spec fails without the fix and passes with it.
diff --git a/spec/accessibility/ax_node_spec.rb b/spec/accessibility/ax_node_spec.rb
new file mode 100644
index 00000000..bef90b1f
--- /dev/null
+++ b/spec/accessibility/ax_node_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+describe Ferrum::Accessibility::AXNode do
+ describe "#value" do
+ before { browser.go_to("/accessibility") }
+
+ it "returns the raw CDP value for a range input as a Numeric (Chrome does not stringify it)" do
+ ax = browser.page.accessibility.node_for(browser.at_css("#volume"))
+ value = ax.value
+
+ # Chrome returns the range value as Integer 7, not the string "7"
+ expect(value).to eq(7)
+ expect(value).to be_a(Numeric)
+ end
+ end
+end
diff --git a/spec/accessibility_spec.rb b/spec/accessibility_spec.rb
index 6ce96549..98809830 100644
--- a/spec/accessibility_spec.rb
+++ b/spec/accessibility_spec.rb
@@ -28,6 +28,18 @@
expect(nodes).to all(be_a(Ferrum::Accessibility::AXNode))
expect(nodes.map(&:role)).to include("button")
end
+
+ it "returns AXNodes for a node in an iframe" do
+ browser.go_to("/accessibility_iframe")
+ frame_node = browser.page.at_css("iframe").frame.at_css("#child_btn")
+
+ nodes = browser.page.accessibility.partial_tree(node: frame_node)
+
+ expect(nodes).to all(be_a(Ferrum::Accessibility::AXNode))
+ expect(nodes).not_to be_empty
+ expect(nodes.map(&:role)).to include("button")
+ expect(nodes.map(&:name)).to include("Frame button")
+ end
end
describe "#snapshot" do
@@ -63,34 +75,11 @@
expect(nodes).to be_empty
end
- end
-
- describe "across an iframe" do
- before { browser.go_to("/accessibility_iframe") }
-
- let(:frame_node) do
- frame = browser.page.at_css("iframe").frame
- frame.at_css("#child_btn")
- end
- it "node_for resolves the AXNode across the frame boundary" do
- ax = frame_node.axnode
-
- expect(ax).to be_a(Ferrum::Accessibility::AXNode)
- expect(ax.role).to eq("button")
- expect(ax.name).to eq("Frame button")
- end
+ it "scopes the query to a node in an iframe" do
+ browser.go_to("/accessibility_iframe")
+ frame_node = browser.page.at_css("iframe").frame.at_css("#child_btn")
- it "partial_tree returns AXNodes for a frame-resident node" do
- nodes = browser.page.accessibility.partial_tree(node: frame_node)
-
- expect(nodes).to all(be_a(Ferrum::Accessibility::AXNode))
- expect(nodes).not_to be_empty
- expect(nodes.map(&:role)).to include("button")
- expect(nodes.map(&:name)).to include("Frame button")
- end
-
- it "query scoped to a frame-resident node returns the button" do
nodes = browser.page.accessibility.query(name: "Frame button", node: frame_node)
expect(nodes).not_to be_empty
@@ -105,21 +94,4 @@
expect(browser.page.accessibility.root).to be_a(Ferrum::Accessibility::AXNode)
end
end
-
- describe "#value" do
- before { browser.go_to("/accessibility") }
-
- it "returns the raw CDP value for a range input as a Numeric (Chrome does not stringify it)" do
- ax = browser.page.accessibility.node_for(browser.at_css("#volume"))
- value = ax.value
-
- # Chrome returns the range value as Integer 7, not the string "7"
- expect(value).to eq(7)
- expect(value).to be_a(Numeric)
- end
- end
-
- it "is reachable from the browser" do
- expect(browser.accessibility).to be_a(described_class)
- end
end
diff --git a/spec/browser/binary_spec.rb b/spec/browser/binary_spec.rb
index 9c6d7623..12f4669f 100644
--- a/spec/browser/binary_spec.rb
+++ b/spec/browser/binary_spec.rb
@@ -28,7 +28,7 @@
ENV["PATHEXT"] = @original_env_pathext
end
- describe "#find" do
+ describe ".find" do
it "finds one binary" do
expect(Ferrum::Browser::Binary.find("bin1")).to eq(bin1)
end
@@ -62,7 +62,7 @@
end
end
- describe "#all" do
+ describe ".all" do
it "finds one binary" do
expect(Ferrum::Browser::Binary.all("bin1")).to eq([bin1])
end
@@ -90,7 +90,7 @@
end
end
- describe "#lazy_find" do
+ describe ".lazy_find" do
it "works lazily" do
enum = Ferrum::Browser::Binary.lazy_find([%w[ls which none]])
diff --git a/spec/browser/version_info_spec.rb b/spec/browser/version_info_spec.rb
index 9dc97b76..64db0197 100644
--- a/spec/browser/version_info_spec.rb
+++ b/spec/browser/version_info_spec.rb
@@ -22,31 +22,31 @@
subject { described_class.new(properties) }
describe "#protocol_version" do
- it "must return the protocolVersion property" do
+ it "returns the protocolVersion property" do
expect(subject.protocol_version).to eq(properties["protocolVersion"])
end
end
describe "#product" do
- it "must return the product property" do
+ it "returns the product property" do
expect(subject.product).to eq(properties["product"])
end
end
describe "#revision" do
- it "must return the revision property" do
+ it "returns the revision property" do
expect(subject.revision).to eq(properties["revision"])
end
end
describe "#user_agent" do
- it "must return the userAgent property" do
+ it "returns the userAgent property" do
expect(subject.user_agent).to eq(properties["userAgent"])
end
end
describe "#js_version" do
- it "must return the jsVersion property" do
+ it "returns the jsVersion property" do
expect(subject.js_version).to eq(properties["jsVersion"])
end
end
diff --git a/spec/browser/xvfb_spec.rb b/spec/browser/xvfb_spec.rb
index 0788399d..78e6abe1 100644
--- a/spec/browser/xvfb_spec.rb
+++ b/spec/browser/xvfb_spec.rb
@@ -5,11 +5,11 @@
let(:xvfb_browser) { Ferrum::Browser.new(default_options.merge(options)) }
let(:default_options) { Hash(headless: true, xvfb: true) }
- context "headless" do
- context "with window_size" do
+ describe ".start" do
+ context "when headless with window_size" do
let(:options) { Hash(window_size: [1400, 1400]) }
- it "allows to run tests configured to xvfb" do
+ it "runs the browser in Xvfb" do
xvfb_browser.go_to(base_url)
expect(xvfb_browser.body).to include("Hello world!")
@@ -22,10 +22,10 @@
end
end
- context "without window_size" do
+ context "when headless without window_size" do
let(:options) { {} }
- it "allows to run tests configured to xvfb" do
+ it "runs the browser in Xvfb" do
xvfb_browser.go_to(base_url)
expect(xvfb_browser.body).to include("Hello world!")
@@ -37,21 +37,21 @@
expect(process_alive?(process.xvfb.pid)).to be(false)
end
end
- end
- context "headful" do
- let(:options) { Hash(headless: false) }
+ context "when headful" do
+ let(:options) { Hash(headless: false) }
- it "allows to run tests configured to xvfb" do
- xvfb_browser.go_to(base_url)
+ it "runs the browser in Xvfb" do
+ xvfb_browser.go_to(base_url)
- expect(xvfb_browser.body).to include("Hello world!")
- expect(process_alive?(process.xvfb.pid)).to be(true)
- expect(process.xvfb.screen_size).to eq("1024x768x24")
- ensure
- xvfb_browser&.quit
- pid = process&.xvfb&.pid
- expect(process_alive?(pid)).to be(false) if pid
+ expect(xvfb_browser.body).to include("Hello world!")
+ expect(process_alive?(process.xvfb.pid)).to be(true)
+ expect(process.xvfb.screen_size).to eq("1024x768x24")
+ ensure
+ xvfb_browser&.quit
+ pid = process&.xvfb&.pid
+ expect(process_alive?(pid)).to be(false) if pid
+ end
end
end
diff --git a/spec/browser_spec.rb b/spec/browser_spec.rb
index 5a278dba..58f9ea7b 100644
--- a/spec/browser_spec.rb
+++ b/spec/browser_spec.rb
@@ -4,256 +4,252 @@
require "ferrum/proxy"
describe Ferrum::Browser do
- describe "#new" do
+ describe ".new" do
let(:logger) { StringIO.new }
- context ":browser_path argument" do
- it "includes the process output in the error" do
- path = "#{PROJECT_ROOT}/spec/support/broken_chrome"
+ it "includes the process output in the error for a broken :browser_path" do
+ path = "#{PROJECT_ROOT}/spec/support/broken_chrome"
- expect do
- Ferrum::Browser.new(browser_path: path)
- end.to raise_error(Ferrum::ProcessTimeoutError) do |e|
- expect(e.output).to include "Broken Chrome error message"
- end
+ expect do
+ Ferrum::Browser.new(browser_path: path)
+ end.to raise_error(Ferrum::ProcessTimeoutError) do |e|
+ expect(e.output).to include "Broken Chrome error message"
end
+ end
- it "supports custom chrome path", :launch do
- original_path = "#{PROJECT_ROOT}/spec/support/chrome_path"
- File.write(original_path, browser.process.path)
+ it "supports custom chrome path", :launch do
+ original_path = "#{PROJECT_ROOT}/spec/support/chrome_path"
+ File.write(original_path, browser.process.path)
- file = "#{PROJECT_ROOT}/spec/support/custom_chrome_called"
- path = "#{PROJECT_ROOT}/spec/support/custom_chrome"
+ file = "#{PROJECT_ROOT}/spec/support/custom_chrome_called"
+ path = "#{PROJECT_ROOT}/spec/support/custom_chrome"
- browser = Ferrum::Browser.new(browser_path: path)
+ browser = Ferrum::Browser.new(browser_path: path)
- # If the correct custom path is called, it will touch the file.
- # We allow at least 10 secs for this to happen before failing.
+ # If the correct custom path is called, it will touch the file.
+ # We allow at least 10 secs for this to happen before failing.
- tries = 0
- until File.exist?(file) || tries == 100
- sleep 0.1
- tries += 1
- end
+ tries = 0
+ until File.exist?(file) || tries == 100
+ sleep 0.1
+ tries += 1
+ end
- expect(File.exist?(file)).to be true
- ensure
- FileUtils.rm_f(original_path)
- FileUtils.rm_f(file)
- browser&.quit
+ expect(File.exist?(file)).to be true
+ ensure
+ FileUtils.rm_f(original_path)
+ FileUtils.rm_f(file)
+ browser&.quit
+ end
+
+ it "supports :logger argument", :launch do
+ browser = Ferrum::Browser.new(logger: logger)
+ browser.go_to(base_url("/console_log"))
+
+ wait_for { logger.string }.to include("Runtime.consoleAPICalled")
+ expect(logger.string).to include("DevTools listening on")
+ expect(logger.string).not_to include("[log] Hello world")
+ ensure
+ browser&.quit
+ end
+
+ it "writes to a Logger at debug level", :launch do
+ browser = Ferrum::Browser.new(logger: Logger.new(logger, level: :debug))
+ browser.go_to(base_url("/console_log"))
+
+ expect(logger.string).to include("DEBUG -- ferrum:browser: DevTools listening on")
+ expect(logger.string).to include("DEBUG -- ferrum:protocol: \n\n▶")
+ ensure
+ browser&.quit
+ end
+
+ it "writes nothing to a Logger above debug level", :launch do
+ browser = Ferrum::Browser.new(logger: Logger.new(logger, level: :info))
+ browser.go_to(base_url("/console_log"))
+
+ expect(logger.string).to be_empty
+ ensure
+ browser&.quit
+ end
+
+ it "logs to $stdout with FERRUM_LOG", :launch do
+ with_env("FERRUM_LOG" => "browser") do
+ expect do
+ Ferrum::Browser.new.quit
+ end.to output(/DevTools listening on/).to_stdout
end
end
- context ":logger argument" do
- it "supports :logger argument", :launch do
+ it "logs nothing with FERRUM_LOG=0", :launch do
+ with_env("FERRUM_LOG" => "0") do
browser = Ferrum::Browser.new(logger: logger)
browser.go_to(base_url("/console_log"))
- wait_for { logger.string }.to include("Runtime.consoleAPICalled")
- expect(logger.string).to include("DevTools listening on")
- expect(logger.string).not_to include("[log] Hello world")
+ expect(logger.string).to be_empty
ensure
browser&.quit
end
+ end
- it "writes to a Logger at debug level", :launch do
- browser = Ferrum::Browser.new(logger: Logger.new(logger, level: :debug))
+ it "logs everything with the deprecated FERRUM_DEBUG", :launch do
+ with_env("FERRUM_DEBUG" => "true") do
+ browser = Ferrum::Browser.new(logger: logger)
browser.go_to(base_url("/console_log"))
- expect(logger.string).to include("DEBUG -- ferrum:browser: DevTools listening on")
- expect(logger.string).to include("DEBUG -- ferrum:protocol: \n\n▶")
+ expect(logger.string).to include("Page.navigate")
+ expect(logger.string).to include("DevTools listening on")
ensure
browser&.quit
end
+ end
- it "writes nothing to a Logger above debug level", :launch do
- browser = Ferrum::Browser.new(logger: Logger.new(logger, level: :info))
+ it "leaves out the channels excluded in FERRUM_LOG", :launch do
+ with_env("FERRUM_LOG" => "*,-browser") do
+ browser = Ferrum::Browser.new(logger: logger)
browser.go_to(base_url("/console_log"))
- expect(logger.string).to be_empty
+ expect(logger.string).to include("Page.navigate")
+ expect(logger.string).not_to include("DevTools listening on")
ensure
browser&.quit
end
+ end
- it "logs to $stdout with FERRUM_LOG", :launch do
- with_env("FERRUM_LOG" => "browser") do
- expect do
- Ferrum::Browser.new.quit
- end.to output(/DevTools listening on/).to_stdout
- end
- end
-
- it "logs nothing with FERRUM_LOG=0", :launch do
- with_env("FERRUM_LOG" => "0") do
- browser = Ferrum::Browser.new(logger: logger)
- browser.go_to(base_url("/console_log"))
-
- expect(logger.string).to be_empty
- ensure
- browser&.quit
- end
- end
-
- it "logs everything with the deprecated FERRUM_DEBUG", :launch do
- with_env("FERRUM_DEBUG" => "true") do
- browser = Ferrum::Browser.new(logger: logger)
- browser.go_to(base_url("/console_log"))
-
- expect(logger.string).to include("Page.navigate")
- expect(logger.string).to include("DevTools listening on")
- ensure
- browser&.quit
- end
- end
-
- it "leaves out the channels excluded in FERRUM_LOG", :launch do
- with_env("FERRUM_LOG" => "*,-browser") do
- browser = Ferrum::Browser.new(logger: logger)
- browser.go_to(base_url("/console_log"))
-
- expect(logger.string).to include("Page.navigate")
- expect(logger.string).not_to include("DevTools listening on")
- ensure
- browser&.quit
- end
- end
-
- it "logs only CDP messages matching FERRUM_LOG", :launch do
- with_env("FERRUM_LOG" => "Page.navigate") do
- browser = Ferrum::Browser.new(logger: logger)
- browser.go_to(base_url("/console_log"))
-
- expect(logger.string).to include(%("method":"Page.navigate"))
- expect(logger.string).to match(/"result":\{"frameId":"\h+","loaderId"/)
- expect(logger.string).not_to include("Runtime.")
- expect(logger.string).not_to include("DevTools listening on")
- ensure
- browser&.quit
- end
- end
-
- it "leaves out CDP messages excluded in FERRUM_LOG", :launch do
- with_env("FERRUM_LOG" => "protocol,-Network.*,-Page.navigate") do
- browser = Ferrum::Browser.new(logger: logger)
- browser.go_to(base_url("/console_log"))
-
- wait_for { logger.string }.to include("Runtime.consoleAPICalled")
- expect(logger.string).not_to include("Network.")
- expect(logger.string).not_to include("Page.navigate")
- expect(logger.string).not_to match(/"result":\{"frameId":"\h+","loaderId"/)
- ensure
- browser&.quit
- end
- end
-
- it "writes CDP messages as JSON alone with FERRUM_LOG_FORMAT=json", :launch do
- with_env("FERRUM_LOG" => "protocol", "FERRUM_LOG_FORMAT" => "json") do
- def logger.tty? = true
- browser = Ferrum::Browser.new(logger: logger)
- browser.go_to(base_url("/console_log"))
+ it "logs only CDP messages matching FERRUM_LOG", :launch do
+ with_env("FERRUM_LOG" => "Page.navigate") do
+ browser = Ferrum::Browser.new(logger: logger)
+ browser.go_to(base_url("/console_log"))
- messages = logger.string.lines.map { |line| JSON.parse(line) }
- expect(messages).to include(a_hash_including("method" => "Page.navigate", "params" => { "url" => base_url("/console_log") }))
- expect(messages).to include(a_hash_including("id" => a_kind_of(Integer), "result" => a_hash_including("frameId")))
- ensure
- browser&.quit
- end
+ expect(logger.string).to include(%("method":"Page.navigate"))
+ expect(logger.string).to match(/"result":\{"frameId":"\h+","loaderId"/)
+ expect(logger.string).not_to include("Runtime.")
+ expect(logger.string).not_to include("DevTools listening on")
+ ensure
+ browser&.quit
end
+ end
- it "annotates CDP messages with FERRUM_LOG_FORMAT=annotated", :launch do
- with_env("FERRUM_LOG_FORMAT" => "annotated") do
- browser = Ferrum::Browser.new(logger: logger)
- browser.go_to(base_url("/console_log"))
+ it "leaves out CDP messages excluded in FERRUM_LOG", :launch do
+ with_env("FERRUM_LOG" => "protocol,-Network.*,-Page.navigate") do
+ browser = Ferrum::Browser.new(logger: logger)
+ browser.go_to(base_url("/console_log"))
- expect(logger.string).to match(%r{^▶ \d+\.\d{3} #\d+ Page\.navigate \{"url":"http://[^"]+/console_log"\} @\h{8}$})
- id = logger.string[/▶ \S+ #(\d+) Page\.navigate/, 1]
- expect(logger.string).to match(/^ ◀ \d+\.\d{3} ##{id} Page\.navigate \d+ms \{"frameId":"\h+",.+\} @\h{8}$/)
- wait_for { logger.string }.to match(/^ ◀ \d+\.\d{3} Runtime\.consoleAPICalled \{"type":"log"/)
- ensure
- browser&.quit
- end
+ wait_for { logger.string }.to include("Runtime.consoleAPICalled")
+ expect(logger.string).not_to include("Network.")
+ expect(logger.string).not_to include("Page.navigate")
+ expect(logger.string).not_to match(/"result":\{"frameId":"\h+","loaderId"/)
+ ensure
+ browser&.quit
end
+ end
- it "colors protocol lines written to a terminal", :launch do
+ it "writes CDP messages as JSON alone with FERRUM_LOG_FORMAT=json", :launch do
+ with_env("FERRUM_LOG" => "protocol", "FERRUM_LOG_FORMAT" => "json") do
def logger.tty? = true
browser = Ferrum::Browser.new(logger: logger)
browser.go_to(base_url("/console_log"))
- expect(logger.string).to include(%("method":"\e[1mPage.navigate\e[0m"))
- expect(logger.string).to include("\e[36m▶\e[0m")
+ messages = logger.string.lines.map { |line| JSON.parse(line) }
+ expect(messages).to include(a_hash_including("method" => "Page.navigate", "params" => { "url" => base_url("/console_log") }))
+ expect(messages).to include(a_hash_including("id" => a_kind_of(Integer), "result" => a_hash_including("frameId")))
ensure
browser&.quit
end
+ end
- it "writes plain text when not writing to a terminal", :launch do
+ it "annotates CDP messages with FERRUM_LOG_FORMAT=annotated", :launch do
+ with_env("FERRUM_LOG_FORMAT" => "annotated") do
browser = Ferrum::Browser.new(logger: logger)
browser.go_to(base_url("/console_log"))
- expect(logger.string).to include("Page.navigate")
- expect(logger.string).not_to include("\e[")
+ expect(logger.string).to match(%r{^▶ \d+\.\d{3} #\d+ Page\.navigate \{"url":"http://[^"]+/console_log"\} @\h{8}$})
+ id = logger.string[/▶ \S+ #(\d+) Page\.navigate/, 1]
+ expect(logger.string).to match(/^ ◀ \d+\.\d{3} ##{id} Page\.navigate \d+ms \{"frameId":"\h+",.+\} @\h{8}$/)
+ wait_for { logger.string }.to match(/^ ◀ \d+\.\d{3} Runtime\.consoleAPICalled \{"type":"log"/)
ensure
browser&.quit
end
+ end
- it "colors lines written anywhere with FERRUM_LOG_COLOR=always", :launch do
- with_env("FERRUM_LOG_COLOR" => "always") do
- browser = Ferrum::Browser.new(logger: logger)
- browser.go_to(base_url("/console_log"))
+ it "colors protocol lines written to a terminal", :launch do
+ def logger.tty? = true
+ browser = Ferrum::Browser.new(logger: logger)
+ browser.go_to(base_url("/console_log"))
- expect(logger.string).to include("\e[36m▶\e[0m")
- ensure
- browser&.quit
- end
- end
+ expect(logger.string).to include(%("method":"\e[1mPage.navigate\e[0m"))
+ expect(logger.string).to include("\e[36m▶\e[0m")
+ ensure
+ browser&.quit
+ end
- it "writes plain text to a terminal with FERRUM_LOG_COLOR=never", :launch do
- with_env("FERRUM_LOG_COLOR" => "never") do
- def logger.tty? = true
- browser = Ferrum::Browser.new(logger: logger)
- browser.go_to(base_url("/console_log"))
+ it "writes plain text when not writing to a terminal", :launch do
+ browser = Ferrum::Browser.new(logger: logger)
+ browser.go_to(base_url("/console_log"))
- expect(logger.string).to include("Page.navigate")
- expect(logger.string).not_to include("\e[")
- ensure
- browser&.quit
- end
- end
+ expect(logger.string).to include("Page.navigate")
+ expect(logger.string).not_to include("\e[")
+ ensure
+ browser&.quit
+ end
- it "keeps the browser working when the logger fails", :launch do
- broken = Object.new
- def broken.puts(*) = raise(IOError, "closed stream")
- browser = nil
+ it "colors lines written anywhere with FERRUM_LOG_COLOR=always", :launch do
+ with_env("FERRUM_LOG_COLOR" => "always") do
+ browser = Ferrum::Browser.new(logger: logger)
+ browser.go_to(base_url("/console_log"))
- expect do
- browser = Ferrum::Browser.new(logger: broken)
- browser.go_to(base_url("/console_log"))
- end.to output(/couldn't write to the logger, IOError: closed stream/).to_stderr
- expect(browser.evaluate("1 + 1")).to eq(2)
+ expect(logger.string).to include("\e[36m▶\e[0m")
ensure
browser&.quit
end
+ end
- it "logs CDP messages nested deeper than JSON's default limit", :launch do
+ it "writes plain text to a terminal with FERRUM_LOG_COLOR=never", :launch do
+ with_env("FERRUM_LOG_COLOR" => "never") do
+ def logger.tty? = true
browser = Ferrum::Browser.new(logger: logger)
- browser.go_to(base_url("/deeply_nested"))
+ browser.go_to(base_url("/console_log"))
- expect(browser.evaluate(%(document.getElementById("text"))).text).to eq("text")
- expect(logger.string).to include("DOM.setChildNodes")
+ expect(logger.string).to include("Page.navigate")
+ expect(logger.string).not_to include("\e[")
ensure
browser&.quit
end
+ end
- it "shortens long strings in logged CDP messages", :launch, skip: Ferrum::Log::VALUE_LENGTH.zero? do
- browser = Ferrum::Browser.new(logger: logger)
+ it "keeps the browser working when the logger fails", :launch do
+ broken = Object.new
+ def broken.puts(*) = raise(IOError, "closed stream")
+ browser = nil
+
+ expect do
+ browser = Ferrum::Browser.new(logger: broken)
browser.go_to(base_url("/console_log"))
- screenshot = browser.screenshot(encoding: :base64)
+ end.to output(/couldn't write to the logger, IOError: closed stream/).to_stderr
+ expect(browser.evaluate("1 + 1")).to eq(2)
+ ensure
+ browser&.quit
+ end
- expect(logger.string).not_to include(screenshot)
- expect(logger.string).to include("#{screenshot[0, Ferrum::Log::VALUE_LENGTH]}... (" \
- "#{screenshot.size - Ferrum::Log::VALUE_LENGTH} more characters)")
- ensure
- browser&.quit
- end
+ it "logs CDP messages nested deeper than JSON's default limit", :launch do
+ browser = Ferrum::Browser.new(logger: logger)
+ browser.go_to(base_url("/deeply_nested"))
+
+ expect(browser.evaluate(%(document.getElementById("text"))).text).to eq("text")
+ expect(logger.string).to include("DOM.setChildNodes")
+ ensure
+ browser&.quit
+ end
+
+ it "shortens long strings in logged CDP messages", :launch, skip: Ferrum::Log::VALUE_LENGTH.zero? do
+ browser = Ferrum::Browser.new(logger: logger)
+ browser.go_to(base_url("/console_log"))
+ screenshot = browser.screenshot(encoding: :base64)
+
+ expect(logger.string).not_to include(screenshot)
+ expect(logger.string).to include("#{screenshot[0, Ferrum::Log::VALUE_LENGTH]}... (" \
+ "#{screenshot.size - Ferrum::Log::VALUE_LENGTH} more characters)")
+ ensure
+ browser&.quit
end
it "keeps the output of the browser process", :launch do
@@ -300,106 +296,80 @@ def broken.puts(*) = raise(IOError, "closed stream")
)
end
- context ":extensions argument" do
- it "extends the browser's world with files", :launch do
- browser = Ferrum::Browser.new(base_url: base_url,
- extensions: [File.expand_path("support/geolocation.js", __dir__)])
-
- browser.go_to("/requiring_custom_extension")
+ it "extends the browser's world with files", :launch do
+ browser = Ferrum::Browser.new(base_url: base_url,
+ extensions: [File.expand_path("support/geolocation.js", __dir__)])
- expect(
- browser.body
- ).to include(%(Location: 1,-1))
-
- expect(
- browser.evaluate(%(document.getElementById("location").innerHTML))
- ).to eq("1,-1")
-
- expect(
- browser.evaluate("navigator.geolocation")
- ).to_not eq(nil)
- ensure
- browser&.quit
- end
+ browser.go_to("/requiring_custom_extension")
- it "extends the browser's world with source", :launch do
- browser = Ferrum::Browser.new(base_url: base_url,
- extensions: [{ source: "window.secret = 'top'" }])
+ expect(
+ browser.body
+ ).to include(%(Location: 1,-1))
- browser.go_to("/requiring_custom_extension")
+ expect(
+ browser.evaluate(%(document.getElementById("location").innerHTML))
+ ).to eq("1,-1")
- expect(browser.evaluate(%(window.secret))).to eq("top")
- ensure
- browser&.quit
- end
+ expect(
+ browser.evaluate("navigator.geolocation")
+ ).to_not eq(nil)
+ ensure
+ browser&.quit
+ end
- it "extends a popup's world before its scripts run", :launch do
- browser = Ferrum::Browser.new(base_url: base_url,
- extensions: [File.expand_path("support/geolocation.js", __dir__)])
- browser.go_to
+ it "extends the browser's world with source", :launch do
+ browser = Ferrum::Browser.new(base_url: base_url,
+ extensions: [{ source: "window.secret = 'top'" }])
- browser.execute(%(window.open("/requiring_custom_extension", "popup")))
- popup, = browser.windows(:last)
+ browser.go_to("/requiring_custom_extension")
- expect(popup.at_css("#location").text).to eq("1,-1")
- ensure
- browser&.quit
- end
+ expect(browser.evaluate(%(window.secret))).to eq("top")
+ ensure
+ browser&.quit
+ end
- it "extends an out-of-process iframe's world before its scripts run", :launch do
- report = <<~JS
- if (window !== top) {
- addEventListener("load", () => parent.postMessage(document.getElementById("location").textContent, "*"));
- }
- JS
- browser = Ferrum::Browser.new(base_url: base_url,
- browser_options: { "site-per-process" => nil },
- extensions: [File.expand_path("support/geolocation.js", __dir__), { source: report }])
- browser.go_to
- browser.execute(<<~JS)
- addEventListener("message", (event) => { window.iframeLocation = event.data });
- document.body.innerHTML = `