Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
p0deje committed Jan 11, 2016
0 parents commit 7c735aa
Show file tree
Hide file tree
Showing 22 changed files with 784 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.bundle/
Gemfile.lock
pkg/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "spec/watirspec"]
path = spec/watirspec
url = git://github.com/watir/watirspec.git
2 changes: 2 additions & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--require spec_helper
--color
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
language: ruby
rvm:
- 2.2.3
13 changes: 13 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Contributor Code of Conduct

As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.

We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.

Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.

This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
source 'https://rubygems.org'

gemspec
22 changes: 22 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Copyright (c) 2016 Alex Rodionov

MIT License

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Watizzle

[Sizzle](http://sizzlejs.com)-based locator engine for [watir-webdriver](https://github.com/watir/watir-webdriver).

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'watizzle'
```

And then execute:

```bash
$ bundle
```

Or install it yourself as:

```bash
$ gem install watizzle
```

## Usage

Require after watir-webdriver, the rest should just work.

```ruby
require 'watir-webdriver'
require 'watizzle'
```

## Development

To install this gem onto your local machine, run `bundle exec rake install`.
To release a new version, update the version number in `version.rb`,
and then run `bundle exec rake release`, which will create a git tag for the version,
push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).

## Specs

Watizzle uses [watirspec](https://github.com/watir/watirspec) for testing, so
you should first fetch it:

```bash
$ git submodule init && git submodule update
```

Now, you can run all specs:

```bash
$ bundle exec rake spec
```

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/p0deje/watizzle.
This project is intended to be a safe, welcoming space for collaboration,
and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
6 changes: 6 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'

RSpec::Core::RakeTask.new(:spec)

task default: :spec
18 changes: 18 additions & 0 deletions lib/watizzle.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require 'watir-webdriver'

require 'watizzle/locators/element/locator'
require 'watizzle/locators/element/selector_builder'
require 'watizzle/locators/element/selector_builder/sizzle'
require 'watizzle/version'

# monkey patch watir-webdriver locators
begin
# silence constant defined warnings
old_verbose, $VERBOSE = $VERBOSE, nil

Watir::Element::Locator = Watizzle::Element::Locator
Watir::Element::SelectorBuilder = Watizzle::Element::SelectorBuilder
Watizzle::Element::Validator = Watir::Element::Validator
ensure
$VERBOSE = old_verbose
end
108 changes: 108 additions & 0 deletions lib/watizzle/locators/element/locator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
require 'erb'

module Watizzle
class Element
class Locator < Watir::Element::Locator
SIZZLE_LOADER_PATH = File.expand_path('../../../sizzle/loader.js.erb', __FILE__)

def initialize(*)
super
inject_sizzle
end

def locate
e = by_id and return e # short-circuit if :id is given
element = find_first_by_multiple
element_validator.validate(element, @selector) if element
rescue Selenium::WebDriver::Error::NoSuchElementError, Selenium::WebDriver::Error::StaleElementReferenceError
nil
end

def locate_all
find_all_by_multiple
end

private

def by_id
return unless id = @selector[:id] and id.is_a? String

selector = @selector.dup
selector.delete(:id)

tag_name = selector.delete(:tag_name)
return unless selector.empty? # multiple attributes

element = retrieve_element(sizzle_locator("##{id}"))
return if element && tag_name && !element_validator.validate(element, selector)

element
end

def find_first_by_multiple
selector = selector_builder.normalized_selector
how, what = selector_builder.build(selector)

if how == :sizzle
retrieve_element(sizzle_locator(what))
else
super
end
end

def find_all_by_multiple
selector = selector_builder.normalized_selector

if selector.key? :index
raise ArgumentError, "can't locate all elements by :index"
end

how, what = selector_builder.build(selector)

if how == :sizzle
retrieve_elements(sizzle_locator(what))
else
super
end
end

def inject_sizzle
loader = ERB.new(File.read(SIZZLE_LOADER_PATH))
execute_script(loader.result)
end

def retrieve_element(locator)
retrieve_elements("#{locator}[0]")
end

def retrieve_elements(locator)
args = [locator]
args << @parent.wd if locate_inside_parent?
execute_script(*args)
end

def execute_script(*args)
browser = case @parent
when Watir::IFrame
# TODO: we should not do that
@parent.tap(&:switch_to!)
when Watir::Element
@parent.browser
else
@parent
end
browser.driver.execute_script(*args)
end

def sizzle_locator(selector)
loc = "return Sizzle('#{selector}'"
loc << ', arguments[0]' if locate_inside_parent?
loc << ')'
end

def locate_inside_parent?
@parent.is_a?(Watir::Element) && !@parent.is_a?(Watir::IFrame)
end
end
end
end
25 changes: 25 additions & 0 deletions lib/watizzle/locators/element/selector_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module Watizzle
class Element
class SelectorBuilder < Watir::Element::SelectorBuilder
def build(selector)
given_xpath_or_css(selector.dup) || build_sizzle_selector(selector)
end

private

def given_xpath_or_css(selector)
# index should not be present for given_xpath_or_css
selector.delete(:index)
super
end

def build_sizzle_selector(selectors)
sizzle_builder.build(selectors)
end

def sizzle_builder
@sizzle_builder ||= Sizzle.new
end
end
end
end
74 changes: 74 additions & 0 deletions lib/watizzle/locators/element/selector_builder/sizzle.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
module Watizzle
class Element
class SelectorBuilder
class Sizzle
def build(selectors)
if selectors.empty?
sizzle = '*'
else
sizzle = ''
sizzle << (selectors.delete(:tag_name) || '')
end

klass = selectors.delete(:class)
if klass
if klass.is_a?(String)
if klass.include? ' '
sizzle << %([class="#{escape_quotes(klass)}"])
else
sizzle << ".#{klass}"
end
else
sizzle << %(:regexp(class, #{klass.inspect}))
end
end

href = selectors.delete(:href)
if href
if href.is_a?(String)
sizzle << %([href~="#{escape_quotes(href)}"])
else
sizzle << %(:regexp(href, #{href.inspect}))
end
end

text = selectors.delete(:text)
if text
if text.is_a?(String)
sizzle << %(:contains("#{escape_quotes(text)}"))
else
sizzle << %(:regexp(text, #{text.inspect}))
end
end

index = selectors.delete(:index)
selectors.each do |key, value|
key = key.to_s.tr("_", "-")

case value
when String, Fixnum
sizzle << %([#{key}="#{escape_quotes(value)}"])
when Regexp
sizzle << %(:regexp(#{key}, #{value.inspect}))
end
end

if index
sizzle << %(:eq(#{index}))
end

p sizzle: sizzle, selectors: selectors if $DEBUG

[:sizzle, sizzle]
end

private

def escape_quotes(str)
# OMG, what's going on here?
str.to_s.gsub('"', '\\\\\\\\\"')
end
end
end
end
end
5 changes: 5 additions & 0 deletions lib/watizzle/sizzle/loader.js.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
if (typeof Sizzle == 'undefined') {
<%= File.read(File.expand_path('vendor/sizzle.min.js')) %>
}

<%= File.read(File.expand_path('lib/watizzle/sizzle/regexp.pseudo.js')) %>
26 changes: 26 additions & 0 deletions lib/watizzle/sizzle/regexp.pseudo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
if (typeof Sizzle.selectors.pseudos.regexp == 'undefined') {
Sizzle.selectors.pseudos.regexp =
Sizzle.selectors.createPseudo(function(selector) {
var selectors = selector.match(/^([^,]+), (.+)$/);
var attr = selectors[1];
var flags = selectors[2].replace(/.*\/([gimy]*)$/, '$1');
var pattern = selectors[2].replace(new RegExp('^/(.*?)/' + flags + '$'), '$1');
var regexp = new RegExp(pattern, flags);
return function(el) {
switch (attr) {
case "text":
var value = el.textContent;
break;
case "href":
var value = el.getAttribute('href');
if (value) {
value = value.replace(/^\s+|\s+$/g, ''); // strip spaces
}
break;
default:
var value = el.getAttribute(attr);
}
return regexp.test(value);
}
});
}
3 changes: 3 additions & 0 deletions lib/watizzle/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Watizzle
VERSION = '0.0.1'
end
Loading

0 comments on commit 7c735aa

Please sign in to comment.