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
30 changes: 30 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
16 changes: 16 additions & 0 deletions spec/accessibility/ax_node_spec.rb
Original file line number Diff line number Diff line change
@@ -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
58 changes: 15 additions & 43 deletions spec/accessibility_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
6 changes: 3 additions & 3 deletions spec/browser/binary_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]])

Expand Down
10 changes: 5 additions & 5 deletions spec/browser/version_info_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 17 additions & 17 deletions spec/browser/xvfb_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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!")
Expand All @@ -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!")
Expand All @@ -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

Expand Down
Loading
Loading