Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add firefox driver #162

Merged
merged 10 commits into from
Feb 29, 2024
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
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
Comment on lines -343 to +403
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this mean we no longer test headless chrome?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, good point. I thought there were more headless chrome specs, but that's not true. I'll have to reinstate some of them and determine why Chrome fails. It's not working for me locally (elementary OS), and also not in a docker container. So I guess we won't be the only ones with he problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the Windows GH workflow also fails using Chrome with the same errors. So something is definitely not right.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added separate specs for headless chrome and headless firefox. Chrome still fails but I'm looking into it.

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
Loading