Skip to content

Commit

Permalink
Add firefox driver (#162)
Browse files Browse the repository at this point in the history
* fix: use crystal 1.5+ for ameba support

* feat: add compatibility for (the favored) docker compose

* feat: add Firefox driver

* test: use headless_firefox driver for failing specs

* fix: ameba styling suggestions

* fix: use separate env var for remote debugging on firefox

* fix: typo and use ENV.fetch

* test: add separate specs for headless chrome and firefox

* chore: reorganise driver arguments

* ci: use beta chrome driver
  • Loading branch information
wout committed Feb 29, 2024
1 parent 2c0c21c commit f89f7b8
Show file tree
Hide file tree
Showing 12 changed files with 213 additions and 68 deletions.
23 changes: 23 additions & 0 deletions .ameba.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# This configuration file was generated by `ameba --gen-config`
# on 2024-02-24 10:06:31 UTC using Ameba version 1.6.1.
# The point is for the user to remove these configuration records
# one by one as the reported problems are removed from the code base.

# Problems found: 1
# Run `ameba --only Naming/AccessorMethodName` for details
Naming/AccessorMethodName:
Description: Makes sure that accessor methods are named properly
Excluded:
- src/lucky_flow/webless/element.cr
Enabled: true
Severity: Convention

# Problems found: 5
# Run `ameba --only Lint/UselessAssign` for details
Lint/UselessAssign:
Description: Disallows useless variable assignments
ExcludeTypeDeclarations: false
Excluded:
- src/lucky_flow.cr
Enabled: true
Severity: Warning
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Lucky Flow CI

on:
push:
branches: [ main ]
branches: [main]
pull_request:
branches: "*"

Expand Down Expand Up @@ -58,7 +58,7 @@ jobs:
- uses: actions/checkout@v3
- uses: browser-actions/setup-chrome@v1
with:
chrome-version: stable
chrome-version: beta
if: matrix.os == 'windows-latest'
- uses: crystal-lang/install-crystal@v1
with:
Expand Down
8 changes: 6 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
FROM crystallang/crystal:1.4.1
FROM crystallang/crystal:1.5.1
WORKDIR /data
EXPOSE 3002

RUN apt-get update \
&& apt-get install -y libnss3 libgconf-2-4 chromium-browser \
&& apt-get install -y \
libnss3 \
libgconf-2-4 \
chromium-browser \
firefox-geckodriver \
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

COPY . /data
13 changes: 8 additions & 5 deletions script/setup
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
set -e
set -o pipefail

if ! command -v docker-compose > /dev/null; then
printf 'Docker and docker-compose are not installed.\n'
if command -v docker-compose > /dev/null; then
docker-compose build
docker-compose run app shards install
elif command -v docker compose > /dev/null; then
docker compose build
docker compose run app shards install
else
printf 'Docker and/or docker-compose are not installed.\n'
printf 'See https://docs.docker.com/compose/install/ for install instructions.\n'
exit 1
fi

docker-compose build

docker-compose run app shards install
6 changes: 5 additions & 1 deletion script/test
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
set -e
set -o pipefail

COMPOSE="docker-compose run app"
if command -v docker-compose > /dev/null; then
COMPOSE="docker-compose run app"
elif command -v docker compose > /dev/null; then
COMPOSE="docker compose run app"
fi

printf "\nrunning specs with 'crystal spec'\n\n"
$COMPOSE crystal spec
Expand Down
2 changes: 1 addition & 1 deletion shard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version: 0.9.2
authors:
- Paul Smith <[email protected]>

crystal: "~> 1.4"
crystal: "~> 1.5"

license: MIT

Expand Down
146 changes: 103 additions & 43 deletions spec/lucky_flow_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -237,60 +237,120 @@ describe LuckyFlow do
flow.should have_element("h1", text: "Target")
end

it "can open screenshots", tags: "headless_chrome" do
flow = LuckyFlow.new
fake_process = FakeProcess
time = Time.utc
context "with headless chrome" do
it "can open screenshots", tags: "headless_chrome" do
flow = LuckyFlow.new
fake_process = FakeProcess
time = Time.utc

flow.open_screenshot(fake_process, time)
flow.open_screenshot(fake_process, time)

fake_process.shell.should be_true
fake_process.command.should eq "open ./tmp/screenshots/#{time.to_unix}.png"
end
fake_process.shell.should be_true
fake_process.command.should eq "open ./tmp/screenshots/#{time.to_unix}.png"
end

it "can open fullsize screenshots", tags: "headless_chrome" do
flow = LuckyFlow.new
fake_process = FakeProcess
time = Time.utc
it "can open fullsize screenshots", tags: "headless_chrome" do
flow = LuckyFlow.new
fake_process = FakeProcess
time = Time.utc

flow.open_screenshot(fake_process, time, fullsize: true)
flow.open_screenshot(fake_process, time, fullsize: true)

fake_process.shell.should be_true
fake_process.command.should eq "open ./tmp/screenshots/#{time.to_unix}.png"
end
fake_process.shell.should be_true
fake_process.command.should eq "open ./tmp/screenshots/#{time.to_unix}.png"
end

it "can reset the session", tags: "headless_chrome" do
flow = visit_page_with <<-HTML
<h1>Title</h1>
HTML
flow.driver.add_cookie("hello", "world")
flow.driver.get_cookie("hello").should eq "world"
it "can reset the session", tags: "headless_chrome" do
flow = visit_page_with <<-HTML
<h1>Title</h1>
HTML
flow.driver.add_cookie("hello", "world")
flow.driver.get_cookie("hello").should eq "world"

LuckyFlow.reset
LuckyFlow.reset

expect_raises Selenium::Error do
flow.driver.get_cookie("hello")
expect_raises Selenium::Error do
flow.driver.get_cookie("hello")
end
end

it "can accept and dismiss alerts", tags: "headless_chrome" do
flow = visit_page_with <<-HTML
<button onclick="createAlert()" flow-id="button" data-count="0">Click Me - 0</button>
<script>
function createAlert() {
alert("Are you sure?");
const button = document.querySelector("[flow-id='button']");
button.innerText = 'Click Me - ' + (++button.dataset.count);
}
</script>
HTML

flow.click("@button")
flow.accept_alert
flow.should have_element("@button", text: "Click Me - 1")
flow.click("@button")
flow.dismiss_alert
flow.should have_element("@button", text: "Click Me - 2")
end
end

it "can accept and dismiss alerts", tags: "headless_chrome" do
flow = visit_page_with <<-HTML
<button onclick="createAlert()" flow-id="button" data-count="0">Click Me - 0</button>
<script>
function createAlert() {
alert("Are you sure?");
const button = document.querySelector("[flow-id='button']");
button.innerText = 'Click Me - ' + (++button.dataset.count);
}
</script>
HTML
context "with headless firefox" do
it "can open screenshots", tags: "headless_firefox" do
flow = LuckyFlow.new
fake_process = FakeProcess
time = Time.utc

flow.click("@button")
flow.accept_alert
flow.should have_element("@button", text: "Click Me - 1")
flow.click("@button")
flow.dismiss_alert
flow.should have_element("@button", text: "Click Me - 2")
flow.open_screenshot(fake_process, time)

fake_process.shell.should be_true
fake_process.command.should eq "open ./tmp/screenshots/#{time.to_unix}.png"
end

it "can open fullsize screenshots", tags: "headless_firefox" do
flow = LuckyFlow.new
fake_process = FakeProcess
time = Time.utc

flow.open_screenshot(fake_process, time, fullsize: true)

fake_process.shell.should be_true
fake_process.command.should eq "open ./tmp/screenshots/#{time.to_unix}.png"
end

it "can reset the session", tags: "headless_firefox" do
flow = visit_page_with <<-HTML
<h1>Title</h1>
HTML
flow.driver.add_cookie("hello", "world")
flow.driver.get_cookie("hello").should eq "world"

LuckyFlow.reset

expect_raises Selenium::Error do
flow.driver.get_cookie("hello")
end
end

it "can accept and dismiss alerts", tags: "headless_firefox" do
flow = visit_page_with <<-HTML
<button onclick="createAlert()" flow-id="button" data-count="0">Click Me - 0</button>
<script>
function createAlert() {
alert("Are you sure?");
const button = document.querySelector("[flow-id='button']");
button.innerText = 'Click Me - ' + (++button.dataset.count);
}
</script>
HTML

flow.click("@button")
flow.accept_alert
flow.should have_element("@button", text: "Click Me - 1")
flow.click("@button")
flow.dismiss_alert
flow.should have_element("@button", text: "Click Me - 2")
end
end

it "can choose option in select input" do
Expand Down Expand Up @@ -340,7 +400,7 @@ describe LuckyFlow do
end
end

it "can hover over an element", tags: "headless_chrome" do
it "can hover over an element", tags: "headless_firefox" do
flow = visit_page_with <<-HTML
<style>
#hidden {
Expand Down
14 changes: 10 additions & 4 deletions src/lucky_flow/selenium/chrome/driver.cr
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ module LuckyFlow::Selenium

private def driver_path
LuckyFlow.settings.driver_path || Webdrivers::Chromedriver.install
rescue err
raise DriverInstallationError.new(err)
rescue e
raise DriverInstallationError.new(e)
end
end
end
Expand All @@ -24,7 +24,13 @@ end

LuckyFlow::Registry.register :headless_chrome do
LuckyFlow::Selenium::Chrome::Driver.new do |config|
remote_debuggin_port = ENV["CHROME_REMOTE_DEBUGGING_PORT"]? || 9222
config.chrome_options.args = ["no-sandbox", "headless", "disable-gpu", "remote-debugging-port=#{remote_debuggin_port}"]
remote_debugging_port = ENV.fetch("CHROME_REMOTE_DEBUGGING_PORT", "9222")
config.chrome_options.args = [
"--no-sandbox",
"--headless",
"--disable-gpu",
"--disable-dev-shm-usage",
"--remote-debugging-port=#{remote_debugging_port}",
]
end
end
12 changes: 9 additions & 3 deletions src/lucky_flow/selenium/driver.cr
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,14 @@ abstract class LuckyFlow::Selenium::Driver < LuckyFlow::Driver
end
end

private def find_elements(strategy : Symbol, query : String) : Array(LuckyFlow::Element)
session.find_elements(strategy, query)
.map { |el| LuckyFlow::Selenium::Element.new(self, query, el).as(LuckyFlow::Element) }
private def find_elements(
strategy : Symbol,
query : String
) : Array(LuckyFlow::Element)
session.find_elements(strategy, query).map do |element|
LuckyFlow::Selenium::Element
.new(self, query, element)
.as(LuckyFlow::Element)
end
end
end
37 changes: 37 additions & 0 deletions src/lucky_flow/selenium/firefox/driver.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module LuckyFlow::Selenium
class Firefox::Driver < Driver
private getter driver : ::Selenium::Driver do
service = ::Selenium::Service.firefox(driver_path: driver_path)
::Selenium::Driver.for(:firefox, service: service)
end

def initialize(&)
super ::Selenium::Firefox::Capabilities.new
yield @capabilities.as(::Selenium::Firefox::Capabilities)
end

private def driver_path
LuckyFlow.settings.driver_path || Webdrivers::Geckodriver.install
rescue e
raise DriverInstallationError.new(e)
end
end
end

LuckyFlow::Registry.register :firefox do
LuckyFlow::Selenium::Firefox::Driver.new { }
end

LuckyFlow::Registry.register :headless_firefox do
LuckyFlow::Selenium::Firefox::Driver.new do |config|
remote_debugging_port = ENV.fetch("FIREFOX_REMOTE_DEBUGGING_PORT", "9222")
config.firefox_options.args = [
"--no-sandbox",
"--headless",
"--disable-gpu",
"--disable-software-rasterizer",
"--disable-dev-shm-usage",
"--start-debugger-server=#{remote_debugging_port}",
]
end
end
4 changes: 2 additions & 2 deletions src/lucky_flow/webless/driver.cr
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ class LuckyFlow::Webless::Driver < LuckyFlow::Driver
end

def find_css(query : String) : Array(LuckyFlow::Element)
@browser.find_css(query).map { |el| element(query, el) }
@browser.find_css(query).map { |elem| element(query, elem) }
end

def find_xpath(query : String) : Array(LuckyFlow::Element)
@browser.find_xpath(query).map { |el| element(query, el) }
@browser.find_xpath(query).map { |elem| element(query, elem) }
end

def current_url : String
Expand Down
Loading

0 comments on commit f89f7b8

Please sign in to comment.