Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ The suite boots a real Chrome and a Sinatra/Puma test app (`spec/support/applica
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.
- 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 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 exception is a context
that carries `let`, hooks (`before`/`after`/`around`) or tags its examples share; it may sit next to plain
examples. Never nest contexts.
- 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
Expand Down
160 changes: 160 additions & 0 deletions spec/console/message_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# frozen_string_literal: true

describe Ferrum::Console::Message do
def log(script)
page.go_to
page.execute(script)
wait_for { page.console.size }.to eq(1)
page.console.last
end

def throw_error
log(%(setTimeout(function thrower() { throw new Error("boom") })))
end

def fail_to_load_image
log(%(document.body.appendChild(document.createElement("img")).src = "/unexist.png"))
end

def log_nested
log(<<~JS)
function inner() { console.log("nested") }
function outer() { inner() }
outer()
JS
end

describe "#text" do
it "joins console arguments with spaces" do
expect(log(%(console.log("a", 1, true))).text).to eq("a 1 true")
end

it "renders objects and arrays as JSON" do
expect(log(%(console.log({ a: 1 }, [1, "b"]))).text).to eq(%({"a":1} [1,"b"]))
end

it "returns the exception text for an uncaught exception" do
expect(throw_error.text).to start_with("Uncaught Error: boom")
end

it "returns the entry text for a browser log entry" do
expect(fail_to_load_image.text).to include("404")
end
end

describe "#args" do
it "returns special numbers as floats" do
expect(log(%(console.log(Infinity, -Infinity, -0))).args).to eq([Float::INFINITY, -Float::INFINITY, -0.0])
end

it "converts the properties of an object preview" do
message = log(%(console.log({ n: 1.5, t: true, f: false }, { u: undefined, z: null, big: 10n, inf: Infinity })))

expect(message.args).to eq(
[{ "n" => 1.5, "t" => true, "f" => false }, { "u" => nil, "z" => nil, "big" => 10, "inf" => Float::INFINITY }]
)
end

it "returns the description of a DOM node" do
expect(log(%(console.log(document.body))).args).to eq(["body"])
end

it "returns an empty array for an uncaught exception" do
expect(throw_error.args).to eq([])
end
end

describe "#stack_trace" do
it "lists the frames innermost first" do
expect(log_nested.stack_trace.map(&:function_name).first(2)).to eq(%w[inner outer])
end

it "returns the frame of the thrown exception" do
expect(throw_error.stack_trace.first.function_name).to eq("thrower")
end
end

describe "#location" do
it "returns the innermost frame" do
message = log_nested

expect(message.location.function_name).to eq("inner")
expect(message.location.line).to eq(1)
expect(message.location.column).to be_positive
end

it "returns the url of a resource that failed to load" do
expect(fail_to_load_image.location.to_s).to eq(base_url("/unexist.png"))
end
end

describe "#severity" do
it "returns :error for console.error" do
expect(log(%(console.error("x"))).severity).to eq(:error)
end

it "returns :error for a failed console.assert" do
expect(log(%(console.assert(false, "x"))).severity).to eq(:error)
end

it "returns :warn for console.warn" do
expect(log(%(console.warn("x"))).severity).to eq(:warn)
end

it "returns :debug for console.debug" do
expect(log(%(console.debug("x"))).severity).to eq(:debug)
end

it "returns :info for console.log" do
expect(log(%(console.log("x"))).severity).to eq(:info)
end
end

describe "#error?" do
it "returns true for console.error" do
expect(log(%(console.error("x")))).to be_error
end

it "returns true for an uncaught exception" do
expect(throw_error).to be_error
end

it "returns false for console.warn" do
expect(log(%(console.warn("x")))).not_to be_error
end
end

describe "#warning?" do
it "returns true for console.warn" do
expect(log(%(console.warn("x")))).to be_warning
end

it "returns false for console.error" do
expect(log(%(console.error("x")))).not_to be_warning
end
end

describe "#match?" do
it "matches a regexp against the text" do
expect(log(%(console.log("Hello world")))).to be_match(/hello W/i)
end

it "checks whether the text includes a string" do
expect(log(%(console.log("Hello world")))).to be_match("lo wo")
end

it "treats a string pattern literally" do
expect(log(%(console.log("Hello world")))).not_to be_match("Hello.world")
end
end

describe "#to_s" do
it "prints the type and text followed by a line per frame" do
message = log_nested

expect(message.to_s.lines.first(3)).to match(
["[log] nested\n", /\A at inner \(.*:1:\d+\)\n\z/, /\A at outer \(.*:2:\d+\)\n\z/]
)
end
end
end
104 changes: 104 additions & 0 deletions spec/console_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,75 @@
expect(console.size).to eq(described_class::LIMIT)
expect(console.first.text).to eq("5")
end

it "returns an enumerator when no block is given" do
page.go_to
page.execute(%(console.log("a"); console.log("b")))

wait_for { console.size }.to eq(2)
enumerator = console.each
expect(enumerator.size).to eq(2)
expect(enumerator.map(&:text)).to eq(%w[a b])
end
end

describe "#to_a" do
it "returns the collected messages oldest first" do
page.go_to
page.execute(%(console.log("a"); console.log("b")))

wait_for { console.to_a.map(&:text) }.to eq(%w[a b])
end

it "returns a copy that doesn't change the console" do
page.go_to("/console_log")
wait_for { console.size }.to eq(1)

console.to_a.clear

expect(console.size).to eq(1)
end
end

describe "#last" do
it "returns the most recent message" do
page.go_to
page.execute(%(console.log("a"); console.log("b")))

wait_for { console.size }.to eq(2)
expect(console.last.text).to eq("b")
end

it "returns nil when no message was collected" do
page.go_to

expect(console.last).to be_nil
end
end

describe "#size" do
it "returns the number of collected messages" do
page.go_to
expect(console.size).to eq(0)

page.execute(%(console.log("a"); console.log("b")))

wait_for { console.size }.to eq(2)
end
end

describe "#empty?" do
it "returns true when no message was collected" do
page.go_to

expect(console).to be_empty
end

it "returns false once a message is collected" do
page.go_to("/console_log")

wait_for { console.empty? }.to be(false)
end
end

describe "#clear" do
Expand All @@ -94,4 +163,39 @@
wait_for { console.map(&:text) }.to eq(["again"])
end
end

describe "#on" do
it "calls the block for every new message" do
texts = Concurrent::Array.new
console.on { |message| texts << message.text }

page.go_to
page.execute(%(console.log("a"); console.log("b")))

wait_for { texts }.to eq(%w[a b])
end

it "returns a distinct id for every listener" do
first = console.on { nil }
second = console.on { nil }

expect(first).not_to eq(second)
end
end

describe "#off" do
it "stops calling the removed listener" do
removed = Concurrent::Array.new
kept = Concurrent::Array.new
id = console.on { |message| removed << message.text }
console.on { |message| kept << message.text }

console.off(id)
page.go_to
page.execute(%(console.log("a")))

wait_for { kept }.to eq(["a"])
expect(removed).to be_empty
end
end
end
Loading
Loading