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

Introduce driver registration #133

Merged
merged 10 commits into from
Jan 14, 2022
Merged

Introduce driver registration #133

merged 10 commits into from
Jan 14, 2022

Conversation

matthewmcgarvey
Copy link
Member

@matthewmcgarvey matthewmcgarvey commented Jan 13, 2022

Fixes #132

This adds in the concept of registering drivers and also allows different drivers to be used in a test suite. The end goal is to allow for an in-memory driver alternative to be used as the default and any that need it can be set to use headless chrome.

This is a breaking change:

  • Several settings were removed that were counter to this new process
  • LuckyFlow::Server was deleted as it didn't serve well in this model
  • LuckyFlow::Spec.setup must be used to correctly configure LuckyFlow for use in specs
  • LuckyFlow::Drivers::HeadlessChrome was deleted as it is now just a chrome driver with slightly different config

There are also bugs in the underlying Selenium library that I found when trying to run specs with headless and non-headless chrome. This are not blocking from the default path of using this library (single driver for all specs).

With the goal of allowing for an in-memory driver alternative, the next step is to remove all direct access to the underlying selenium library since the new driver won't be provided through selenium.

@@ -5,6 +5,8 @@ require "./support/**"

include LuckyFlow::Expectations

LuckyFlow::Spec.setup
Copy link
Member Author

Choose a reason for hiding this comment

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

This sets up the lucky flow driver if one is requested through tags on the spec, and then resets back to the default driver after the spec is run.

It also has a callback for shutting down the drivers once all specs finish.

Copy link
Member

Choose a reason for hiding this comment

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

I wonder if there would be a way to force people to call this... Or like, you try to run some specs, but you forgot to add this so you get a compile-time error telling you to add it 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't know about "force", but another option would be to have luckyflow/spec unrequired by the library but requires luckyflow and does the Spec setup right in the file. Then we document that in order to use the library in your specs, you must require "lucky_flow/spec" after you require "spec" and it will just work. It could potentially be a pattern that we could support spectator in the future if we want to (just require "lucky_flow/spectator"). Thoughts on that?

@@ -0,0 +1,12 @@
module Spec
module Item
def all_tags : Set(String)
Copy link
Member Author

Choose a reason for hiding this comment

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

Tags on a spec don't include parent contexts (context, describe) so this monkey-patched method is to facilitate that.

@@ -52,9 +74,9 @@ class LuckyFlow

def take_screenshot(filename : String = generate_screenshot_filename, fullsize : Bool = true)
if fullsize
with_fullsized_page { session.screenshot(filename) }
with_fullsized_page { driver.screenshot(filename) }
Copy link
Member Author

Choose a reason for hiding this comment

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

Switched to the driver instead of the session so that the driver could make the screenshot directory if it's missing

abstract def stop

def reset : Nil
@session.try &.cookie_manager.delete_all_cookies
Copy link
Member Author

Choose a reason for hiding this comment

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

we use @session instead of the getter so that it doesn't start a session if there wasn't one already

Copy link
Member

Choose a reason for hiding this comment

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

What happens if this is called before session is set?

Copy link
Member Author

Choose a reason for hiding this comment

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

It will be nil, so the .try will avoid an error


private def browser_binary : String?
LuckyFlow.settings.browser_binary
LuckyFlow::Registry.register :headless_chrome do
Copy link
Member Author

Choose a reason for hiding this comment

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

Don't like this config? Just call LuckyFlow::Registry.register :headless_chrome do ... end with your own setup and it will overwrite this one 👍

@@ -0,0 +1,21 @@
class LuckyFlow::Registry
@@registry = Hash(String, Proc(LuckyFlow::Driver)).new
@@running_registry = Hash(String, LuckyFlow::Driver).new
Copy link
Member Author

Choose a reason for hiding this comment

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

@@registry is for drivers that can be used but aren't made yet,
@@running_registry are drivers that have been created and should be used before making a new one

module LuckyFlow::Spec
macro setup
Spec.around_each do |spec|
if driver_name = (spec.example.all_tags & LuckyFlow::Registry.available).first?
Copy link
Member Author

Choose a reason for hiding this comment

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

In Rspec, you can pass key-value pairs in it's but in Crystal you only have tags which are a list of strings. So instead of requiring passing something like driver: :chrome, you have to do tags: "chrome" instead and then we take the overlap of the tags you passed with the available browsers you can use. If for some reason you do tags: ["headless_chrome", "chrome"], there'd be an overlap of 2 but we take the first


def screenshot(path : String)
FileUtils.mkdir_p(File.dirname(path))
session.screenshot(path)
Copy link
Member

Choose a reason for hiding this comment

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

should this also use the instance variable?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, the only places I used the instance variable was places where if it was never started, there's nothing to stop/reset

module Item
def all_tags : Set(String)
all_tags = tags || Set(String).new
temp = parent
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 include all parents, or just the direct parent?

describe Thing do
  context "when thing", tags: ["chrome"] do
    describe "is a thing" do
      it "does stuff" do
      end
    end
  end
end

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, in the if below I call all_tags on the parent which means it will fetch all tags of ever parent until it reaches the Spec::RootContext which is not a Spec::Item

Copy link
Member

Choose a reason for hiding this comment

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

ah, ok. I see what it's doing now. Can we rename the internal variable so it doesn't shadow the method name?

Copy link
Member Author

Choose a reason for hiding this comment

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

I ended up using the same naming convention we were recommended to use over here https://github.com/luckyframework/avram/blob/ac281f490a85283eaf8b74fd23357dd70a6d7f22/src/ext/db/connection.cr#L7

with an underscore and the name of the shard so in this case _lucky_flow_all_tags. I also added # :nodoc:. It's not a very clear name but it doesn't need to be

Copy link
Member

Choose a reason for hiding this comment

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

Cool, I like that.

Copy link
Member

@jwoertink jwoertink left a comment

Choose a reason for hiding this comment

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

I think this looks good!

@matthewmcgarvey matthewmcgarvey merged commit da9c947 into master Jan 14, 2022
@matthewmcgarvey matthewmcgarvey deleted the driver-registration branch January 14, 2022 16:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Can only use one driver for all specs
2 participants