capybara-async_runner 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7e2d94d0d6867b6a67a5eb65b0436f8ad1e81cde
4
+ data.tar.gz: 7d47e6c4a489840d793272dfb23461f2a5f0d204
5
+ SHA512:
6
+ metadata.gz: 2ba028e72e92957aebcba3509bbdc3d552937be71e1b954214ab2c0b03e9192a6922a7509b53807d9af1bc7de51b1f61d95a089ee3fb3805d44eaded7ca973aa
7
+ data.tar.gz: 26c9975d86b4e04c1c22034769f12cc1dce912bc179b0038b8497d3ebfc41c74c6fffc7143300fbebbe1603ce33722abf84a80da7d63965b5d44fe826f0c396b
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .ruby-version
11
+ .ruby-gemset
12
+ .rvmrc
13
+ .rspec
14
+
data/.travis.yml ADDED
@@ -0,0 +1 @@
1
+ before_install: gem install bundler -v 1.9.9
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in capybara-async_runner.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Ilya Bylich
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # Capybara::AsyncRunner
2
+
3
+ [![Build Status](https://travis-ci.org/iliabylich/capybara-async-runner.svg?branch=master)](https://travis-ci.org/iliabylich/capybara-async-runner)
4
+ [![Code Climate](https://codeclimate.com/github/iliabylich/capybara-async-runner/badges/gpa.svg)](https://codeclimate.com/github/iliabylich/capybara-async-runner)
5
+ [![Inline docs](http://inch-ci.org/github/iliabylich/capybara-async-runner.svg?branch=master)](http://inch-ci.org/github/iliabylich/capybara-async-runner)
6
+
7
+ This is a ruby gem for running asynchronous JavaScript code synchronously with Capybara.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'capybara-async_runner'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install capybara-async_runner
24
+
25
+ ## Usage
26
+
27
+ Read [this](http://ilyabylich.svbtle.com/capybara-and-asynchronous-stuff) blog post first.
28
+ Then check out `examples` dir.
29
+
30
+ ## How to use
31
+
32
+ + Setup a directory with command templates
33
+
34
+ ``` ruby
35
+ Capybara::AsyncRunner.setup do |config|
36
+ config.commands_directory = Rails.root.join('directory/with/templates')
37
+ end
38
+ ```
39
+
40
+ + Create a command
41
+
42
+ ``` ruby
43
+ class MyCommand < Capybara::AsyncRunner::Command
44
+ # specify the name
45
+ self.command_name = :my_command
46
+ # specify a path to template
47
+ self.template = 'template_name'
48
+ # specify a response
49
+ response :done
50
+ end
51
+ ```
52
+
53
+ + Create a template file
54
+
55
+ ``` javascript
56
+ // directory/with/templates/template_name.js.erb
57
+ yourCode(function(data) {
58
+ <%= done(js[:data]) %>
59
+ })
60
+ ```
61
+
62
+ + Call the command
63
+
64
+ ``` ruby
65
+ Capybara::AsyncRunner.run(:my_command)
66
+ # => data from your script
67
+ ```
68
+
69
+ ## Contributing
70
+
71
+ 1. Fork it ( https://github.com/[my-github-username]/capybara-async_runner/fork )
72
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
73
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
74
+ 4. Push to the branch (`git push origin my-new-feature`)
75
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: [:spec]
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "capybara/async_runner"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'capybara/async_runner/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "capybara-async_runner"
8
+ spec.version = Capybara::AsyncRunner::VERSION
9
+ spec.authors = ["Ilya Bylich"]
10
+ spec.email = ["ibylich@gmail.com"]
11
+
12
+ spec.summary = %q{Gem for running ascynrhonous jobs in Capybara.}
13
+ spec.homepage = "https://github.com/iliabylich/capybara-async-runner"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.9"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency 'rspec', '~> 3.2.0'
24
+ spec.add_development_dependency 'rspec-its', '~> 1.2.0'
25
+ spec.add_development_dependency 'capybara'
26
+ spec.add_development_dependency 'poltergeist'
27
+ spec.add_development_dependency 'pry'
28
+ end
@@ -0,0 +1,12 @@
1
+ class IndexDB::Commands::Insert < Capybara::AsyncRunner::Command
2
+ FailedToInsertData = Class.new(StandardError)
3
+
4
+ self.command_name = 'indexeddb:insert'
5
+ self.file_to_run = 'commands/insert'
6
+
7
+ response :error do |response|
8
+ raise FailedToInsertData, response
9
+ end
10
+
11
+ response :success
12
+ end
@@ -0,0 +1,11 @@
1
+ class IndexDB::Commands::Query < Capybara::AsyncRunner::Command
2
+ ErrorDuringQuerying = Class.new(StandardError)
3
+
4
+ self.command_name = 'indexeddb:query'
5
+ self.file_to_run = 'commands/query'
6
+
7
+ response :success
8
+ response :error do |response|
9
+ raise ErrorDuringQuerying, response
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ class IndexDB::WrapperInitializer < Capybara::AsyncRunner::Command
2
+ self.command_name = 'indexeddb:wrapper:initialize'
3
+ self.file_to_run = 'wrapper/initialize'
4
+
5
+ response :error do |response|
6
+ raise "Client error: #{response}"
7
+ end
8
+
9
+ response :success
10
+ end
@@ -0,0 +1,9 @@
1
+ class IndexDB::WrapperLoader < Capybara::AsyncRunner::Command
2
+ self.command_name = 'indexeddb:wrapper:inject'
3
+ self.file_to_run = 'wrapper/inject'
4
+
5
+ response :success
6
+ response :error do
7
+ raise 'Failed to inject script'
8
+ end
9
+ end
@@ -0,0 +1,139 @@
1
+ require 'pathname'
2
+ ROOT = Pathname.new(File.expand_path('../../..', __FILE__))
3
+ $: << ROOT.join('lib')
4
+ $: << EXAMPLE_ROOT = ROOT.join('examples/indexeddb')
5
+
6
+ require 'capybara'
7
+ require 'capybara/async_runner'
8
+ require 'capybara/poltergeist'
9
+ require 'pry'
10
+
11
+ Capybara.run_server = false
12
+ Capybara.default_driver = :poltergeist
13
+
14
+ Capybara::AsyncRunner.setup do |config|
15
+ config.commands_directory = EXAMPLE_ROOT.join('templates')
16
+ end
17
+
18
+ module IndexDB
19
+ URL = "https://raw.githubusercontent.com/dfahlander/Dexie.js/master/src/Dexie.js"
20
+
21
+ require 'commands/wrapper_loader'
22
+ require 'commands/wrapper_initializer'
23
+
24
+ module Commands
25
+ require 'commands/insert'
26
+ require 'commands/query'
27
+ end
28
+ end
29
+
30
+ # We can't run indexeddb-related code in about:blank
31
+ Capybara.current_session.visit('http://google.com')
32
+
33
+ # Inject the script
34
+ Capybara::AsyncRunner.run('indexeddb:wrapper:inject', url: IndexDB::URL)
35
+
36
+ # Run some initialization
37
+ Capybara::AsyncRunner.run('indexeddb:wrapper:initialize')
38
+
39
+ # And do some stuff
40
+ user_data = { name: 'Some Name' }
41
+
42
+ user_id = Capybara::AsyncRunner.run('indexeddb:insert', store: 'users', data: user_data)
43
+ p "User ID: #{user_id}"
44
+
45
+ methods = [
46
+ { method: 'where', arguments: ['id']},
47
+ { method: 'equals', arguments: [user_id] },
48
+ { method: 'toArray', arguments: [] }
49
+ ]
50
+
51
+ p Capybara::AsyncRunner.run('indexeddb:query', store: 'users', methods: methods)
52
+
53
+ # And even write some small wrapper
54
+ #
55
+ #
56
+ module IndexDB::DSL
57
+ def current_scope
58
+ @current_scope ||= []
59
+ end
60
+
61
+ IDB_METHODS = {
62
+ above: :above,
63
+ aboveOrEqual: :above_or_equal,
64
+ anyOf: :any_of,
65
+ below: :below,
66
+ belowOrEqual: :below_or_equal,
67
+ between: :between,
68
+ equals: :equals,
69
+ equalsIgnoreCase: :equals_ignore_case,
70
+ startsWith: :starts_with,
71
+ startsWithAnyOf: :starts_with_any_of,
72
+ startsWithIgnoreCase: :starts_with_ignore_case,
73
+ toArray: :to_a,
74
+ where: :where
75
+ }
76
+
77
+ IDB_METHODS.each do |js_method_name, ruby_method_name|
78
+ define_method ruby_method_name do |*arguments|
79
+ current_scope << { method: js_method_name, arguments: arguments }
80
+ self
81
+ end
82
+ end
83
+
84
+ module ModelSupport
85
+ IndexDB::DSL::IDB_METHODS.each do |_, method_name|
86
+ define_method method_name do |*arguments|
87
+ IndexDB::ChainedScope.new(self).send(method_name, *arguments)
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ class IndexDB::ChainedScope
94
+ include IndexDB::DSL
95
+
96
+ def initialize(model)
97
+ @model = model
98
+ end
99
+
100
+ def get
101
+ collection = Capybara::AsyncRunner.run('indexeddb:query', store: @model.store, methods: current_scope)
102
+ collection.map do |record|
103
+ @model.new(record)
104
+ end
105
+ end
106
+ end
107
+
108
+ class IndexDB::Model
109
+ extend IndexDB::DSL::ModelSupport
110
+ attr_accessor :data
111
+
112
+ def initialize(data)
113
+ @data = data
114
+ end
115
+
116
+ class << self
117
+ attr_accessor :store
118
+
119
+ def all
120
+ where('id').above(0).to_a.get
121
+ end
122
+
123
+ def find(record_id)
124
+ where('id').equals(record_id).to_a.get.first
125
+ end
126
+
127
+ def count
128
+ all.count
129
+ end
130
+ end
131
+ end
132
+
133
+ class User < IndexDB::Model
134
+ self.store = :users
135
+ end
136
+
137
+ p User.all
138
+ p User.find(8)
139
+ p User.count
@@ -0,0 +1,8 @@
1
+ var store = window.db['<%= data[:store] %>'];
2
+ var dataToInsert = JSON.parse(<%= data[:data].to_json.inspect %>);
3
+
4
+ store.add(dataToInsert).then(function(user_id) {
5
+ <%= success(js[:user_id]) %>
6
+ }).catch(function(err) {
7
+ <%= error(js[:err]) %>
8
+ })
@@ -0,0 +1,15 @@
1
+ var store = window.db['<%= data[:store] %>'];
2
+ var methodsPayload = JSON.parse(<%= data[:methods].to_json.inspect %>);
3
+
4
+ methodsPayload.forEach(function(methodPayload) {
5
+ var method = methodPayload['method'];
6
+ var arguments = methodPayload['arguments'];
7
+ console.log("Executing", method, "with", arguments);
8
+ store = store[method].apply(store, arguments);
9
+ });
10
+
11
+ store.then(function(data) {
12
+ <%= success(js[:data]) %>
13
+ }).catch(function(err) {
14
+ <%= error(js[:err]) %>
15
+ })
@@ -0,0 +1,18 @@
1
+ window.errorHandler = function(err) {
2
+ <%= error(js[:err]) %>
3
+ }
4
+
5
+ if (typeof(Dexie) === 'undefined') {
6
+ errorHandler('No Dexie');
7
+ } else {
8
+ window.db = new Dexie('demo91');
9
+ window.db.version(1).stores({
10
+ users: '++id,name'
11
+ });
12
+
13
+ window.db.open().catch(function(err) {
14
+ errorHandler('failed to open indexedDB ' + err);
15
+ }).then(function() {
16
+ <%= success %>
17
+ });
18
+ }
@@ -0,0 +1,29 @@
1
+ function isWrapperReady() {
2
+ return (typeof(Dexie) !== 'undefined');
3
+ }
4
+
5
+ function injectScript(src) {
6
+ var fileref = document.createElement('script')
7
+   fileref.setAttribute("type","text/javascript")
8
+   fileref.setAttribute("src", src);
9
+ document.getElementsByTagName("head")[0].appendChild(fileref)
10
+ }
11
+
12
+ if (isWrapperReady()) {
13
+ <%= success %>
14
+ } else {
15
+ injectScript("<%= data[:url] %>");
16
+
17
+ setInterval(function() {
18
+ if (isWrapperReady()) {
19
+ <%= success %>
20
+ }
21
+ }, 100);
22
+
23
+ setTimeout(function () {
24
+ if (typeof(Dexie) === 'undefined') {
25
+ <%= error %>
26
+ }
27
+ }, 3000);
28
+ }
29
+
@@ -0,0 +1,36 @@
1
+ # Don't forget to run 'bundle install' before running this script
2
+ #
3
+ require 'pathname'
4
+ ROOT = Pathname.new(File.expand_path('../../..', __FILE__))
5
+ $: << ROOT.join('lib')
6
+
7
+ require 'capybara'
8
+ require 'capybara/async_runner'
9
+
10
+ # Base gem configuration
11
+ Capybara::AsyncRunner.setup do |config|
12
+ config.commands_directory = ROOT.join('examples/simple')
13
+ end
14
+
15
+ # Defining a command
16
+ class TestCommand < Capybara::AsyncRunner::Command
17
+ # global command name
18
+ self.command_name = :json_and_timeout
19
+
20
+ # .js.erb file in directory specified above
21
+ self.file_to_run = 'template'
22
+
23
+ response :parsed_json do |data|
24
+ JSON.parse(data)
25
+ end
26
+ end
27
+
28
+ # I'm using poltergeist as a webdriver here, but you can use whatever you want
29
+ require 'capybara/poltergeist'
30
+
31
+ # My command is totally abstract, so I'm running it in context of 'about:blank' page
32
+ Capybara.run_server = false
33
+ Capybara.default_driver = :poltergeist
34
+
35
+ p Capybara::AsyncRunner.run(:json_and_timeout)
36
+ # or TestCommand.new.invoke
@@ -0,0 +1,4 @@
1
+ setTimeout(function() {
2
+ var json = JSON.stringify([1,2,3]);
3
+ <%= parsed_json(js[:json]) %>
4
+ })
@@ -0,0 +1,49 @@
1
+ require "capybara/async_runner/version"
2
+
3
+ module Capybara
4
+ module AsyncRunner
5
+ FailedToFetchResult = Class.new(StandardError)
6
+
7
+ autoload :Configuration, 'capybara/async_runner/configuration'
8
+ autoload :Command, 'capybara/async_runner/command'
9
+ autoload :Env, 'capybara/async_runner/env'
10
+ autoload :JsBuilder, 'capybara/async_runner/js_builder'
11
+ autoload :WaitHelper, 'capybara/async_runner/wait_helper'
12
+ autoload :Registry, 'capybara/async_runner/registry'
13
+
14
+ # Returns current gem configuration.
15
+ #
16
+ # @return [Capybara::AsyncRunner::Configuration]
17
+ #
18
+ def self.config
19
+ @config ||= Configuration.new
20
+ end
21
+
22
+ # Yields current configuration
23
+ #
24
+ # @yield [Capybara::AsyncRunner::Configuration]
25
+ #
26
+ # @example
27
+ # Capybara::AsyncRunner.setup do |config|
28
+ # config.commands_directory = Rails.root.join('spec/support/async_runner')
29
+ # end
30
+ #
31
+ def self.setup
32
+ yield config
33
+ end
34
+
35
+ # Runs command that has provided +command_name+ and passes provided +data+
36
+ #
37
+ # @example
38
+ # class MyCoolCommand < Capybara::AsyncRunner::Command
39
+ # self.command_name = 'cool_command'
40
+ # self.file_to_run = 'path/to/cool/command'
41
+ # end
42
+ #
43
+ # Capybara::AsyncRunner.run(:cool_command)
44
+ #
45
+ def self.run(command_name, data = {})
46
+ Registry[command_name].new(data).invoke
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,59 @@
1
+ require 'securerandom'
2
+ require 'capybara/async_runner/commands/configuration'
3
+ require 'capybara/async_runner/commands/responders'
4
+ require 'capybara/async_runner/commands/templates'
5
+
6
+ # A common class for defining your commands
7
+ #
8
+ # Every command MUST have a name and a .js.erb file
9
+ #
10
+ # @example
11
+ # # spec/support/async_runner/commands/some_command.rb
12
+ # #
13
+ # class SomeCommand < Capybara::AsyncRunner::Command
14
+ # self.command_name = :some_command_name
15
+ # self.file_to_run = 'your/path/to/file/without/extension'
16
+ #
17
+ # response :done
18
+ # response :fail do |data|
19
+ # JSON.parse(data)
20
+ # end
21
+ # end
22
+ #
23
+ # SomeCommand.new({}).invoke
24
+ # # => result
25
+ #
26
+ class Capybara::AsyncRunner::Command
27
+ include Capybara::AsyncRunner::Commands::Configuration
28
+ include Capybara::AsyncRunner::Commands::Responders
29
+ include Capybara::AsyncRunner::Commands::Templates
30
+
31
+ # @param data [Hash] data is available in template through <%= data %>
32
+ #
33
+ def initialize(data = {})
34
+ @uuid = SecureRandom.uuid
35
+ @env = Capybara::AsyncRunner::Env.new(uuid, data, responders)
36
+ end
37
+
38
+ attr_reader :uuid, :env
39
+
40
+ # Invokes the command and returns its result
41
+ #
42
+ # @see Capybara::AsyncRunner::JsBuilder
43
+ # @see Capybara::AsyncRunner::Env
44
+ #
45
+ def invoke
46
+ js_builder.result
47
+ end
48
+
49
+ private
50
+
51
+ def js_builder
52
+ @js_builder ||= Capybara::AsyncRunner::JsBuilder.new(@env, erb)
53
+ end
54
+
55
+ def self.inherited(klass)
56
+ super
57
+ Capybara::AsyncRunner::Registry << klass
58
+ end
59
+ end
@@ -0,0 +1,74 @@
1
+ # Module for confoiguring your commands
2
+ #
3
+ # @example
4
+ # class SomeCommand < Capybara::AsyncRunner::Command
5
+ # self.command_name = 'my_command_name'
6
+ # # (use underscores, your can later call them by passing symbols)
7
+ # self.file_to_run = 'path/to/your/file'
8
+ # end
9
+ #
10
+ module Capybara::AsyncRunner::Commands
11
+ module Configuration
12
+ def self.included(base)
13
+ base.extend(ClassMethods)
14
+ end
15
+
16
+ module ClassMethods
17
+ # Returns configured command name or raises error
18
+ #
19
+ # @return [String, Symbol]
20
+ #
21
+ # @raise NotImplementedError
22
+ #
23
+ def command_name
24
+ store[:command_name] or
25
+ raise NotImplementedError, "You need to define self.command_name = ... in #{self}"
26
+ end
27
+
28
+ # Saves provided +command_name+
29
+ #
30
+ # @param command_name [String]
31
+ #
32
+ def command_name=(command_name)
33
+ store[:command_name] = command_name
34
+ end
35
+
36
+ # Returns configured filepath or raises error
37
+ #
38
+ # @return [String, Symbol]
39
+ #
40
+ # @raise NotImplementedError
41
+ #
42
+ def file_to_run
43
+ store[:file_to_run] or
44
+ raise NotImplementedError, "You need to define self.file_to_run = ... in #{self}"
45
+ end
46
+
47
+ # Stores provided relative +file_to_run+ (don't include directory and extension)
48
+ #
49
+ # @param file_to_run [String]
50
+ #
51
+ def file_to_run=(file_to_run)
52
+ store[:file_to_run] = file_to_run
53
+ end
54
+
55
+ # @private
56
+ #
57
+ # Inherits all configuration from parent
58
+ #
59
+ def inherited(klass)
60
+ super
61
+ return if self == Capybara::AsyncRunner::Command
62
+ klass.command_name = self.command_name
63
+ klass.file_to_run = self.file_to_run
64
+ end
65
+
66
+ private
67
+
68
+ # @private
69
+ def store
70
+ @store ||= {}
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,62 @@
1
+ # Module for defining your responders
2
+ #
3
+ # Responder is a method that you should call in you .js code
4
+ #
5
+ # @example
6
+ # class MyCommand < Capybara::AsyncRunner::Commands
7
+ # response :done
8
+ # response :fail, as: :callback
9
+ # response :parsed_json do |data|
10
+ # JSON.parse(data)
11
+ # end
12
+ #
13
+ # # template
14
+ # if (done) {
15
+ # <%= done %>
16
+ # } else if (fail) {
17
+ # afterFail(<%= fail %>)
18
+ # } eles if (returnedJSON) {
19
+ # <%= parsed_json(js[:returnedJSON]) %>
20
+ # }
21
+ #
22
+ # Only one responder can be executed for 1 script during 1 invokation
23
+ #
24
+ module Capybara::AsyncRunner::Commands
25
+ module Responders
26
+ def self.included(base)
27
+ base.send(:include, InstanceMethods)
28
+ base.extend(ClassMethods)
29
+ end
30
+
31
+ module InstanceMethods
32
+ def responders
33
+ self.class.responders
34
+ end
35
+ end
36
+
37
+ module ClassMethods
38
+ def responders
39
+ store[:responders] ||= {}
40
+ end
41
+
42
+ BLANK_PROXY = proc { |value| value }
43
+
44
+ # Defines a responder for script
45
+ #
46
+ # @param method_name [String, Symbols]
47
+ # @param options [Hash]
48
+ # @option options [Symbol] :as can be :callback
49
+ # @yield raw_data from script
50
+ #
51
+ # raw_data will be returned if no block provided
52
+ #
53
+ def response(method_name, options = {}, &block)
54
+ mapping = {
55
+ options: options,
56
+ processor: block || BLANK_PROXY
57
+ }
58
+ responders[method_name] = mapping
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,11 @@
1
+ # Just a module for reading erb files
2
+ #
3
+ module Capybara::AsyncRunner::Commands
4
+ module Templates
5
+ def erb
6
+ fullname = self.class.file_to_run + '.js.erb'
7
+ filepath = Capybara::AsyncRunner.config.commands_directory.join(fullname)
8
+ File.read(filepath)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ # Class for configuring a gem
2
+ #
3
+ # @example
4
+ # config = Capybara::AsyncRunner::Configuration
5
+ # config.commands_directory = 'some/dir'
6
+ #
7
+ class Capybara::AsyncRunner::Configuration
8
+ def initialize
9
+ @data = {}
10
+ end
11
+
12
+ # A DSL for defining accessors with default values
13
+ #
14
+ # @example
15
+ # config_accessor :access_name # no default value
16
+ # config_accessor :timeout, 12
17
+ #
18
+ def self.config_accessor(attribute_name, default = nil)
19
+ define_method attribute_name do
20
+ @data[attribute_name] || default
21
+ end
22
+
23
+ define_method "#{attribute_name}=" do |value|
24
+ @data[attribute_name] = value
25
+ end
26
+ end
27
+
28
+ # Returns directory that contains all command files
29
+ #
30
+ config_accessor :commands_directory
31
+
32
+ # Drops all configured values
33
+ #
34
+ def reset!
35
+ @data = {}
36
+ end
37
+ end
@@ -0,0 +1,78 @@
1
+ # Internal class for building environment for rendering .js.erb code
2
+ #
3
+ class Capybara::AsyncRunner::Env
4
+ # @param uuid [String] just a uuid of running command
5
+ # @param data [Hash] data that is available in .erb through <%= data[:something] %>
6
+ # @param responders [Hash<Symbol, Hash>] mapping method_name => options
7
+ #
8
+ # For responders:
9
+ # @see Capybara::AsyncRunner::Commands::Responders
10
+ #
11
+ def initialize(uuid, data, responders)
12
+ @uuid = uuid
13
+ @data = data
14
+ @responders = responders
15
+ end
16
+ attr_reader :data, :responders, :uuid
17
+
18
+ # Returns a js environment that can be used for fetching data from the code on-the-fly
19
+ #
20
+ # @example
21
+ # # spec/support/async_runner/templates/command1.js.erb
22
+ # var jsLocal = 123;
23
+ # <%= done(js[:jsLocal] %>
24
+ #
25
+ def js_environment
26
+ Hash.new do |h, k|
27
+ k.to_s
28
+ end
29
+ end
30
+ alias_method :js, :js_environment
31
+
32
+ # Delegates a method to responder
33
+ #
34
+ # @example
35
+ # <%= responder1(js[:var1]) %>
36
+ #
37
+ # @see Capybara::AsyncRunner::Commands::Responders
38
+ #
39
+ def method_missing(method_name, *args)
40
+ if responders.include?(method_name)
41
+ response_method_for(method_name, *args)
42
+ else
43
+ super
44
+ end
45
+ end
46
+
47
+ # @private
48
+ def respond_to_missing?(method_name, include_private = false)
49
+ super || responders.include?(method_name)
50
+ end
51
+
52
+ # Returns local binding that is used for rendering (ERB.new(template).result(binding))
53
+ #
54
+ # @return [Binding]
55
+ #
56
+ def local_binding
57
+ binding
58
+ end
59
+
60
+ attr_reader :responders
61
+
62
+ private
63
+
64
+ def response_method_for(method_name, *args)
65
+ options = responders[method_name][:options]
66
+ if options[:as] == :callback
67
+ <<-JS
68
+ function() {
69
+ var args = Array.prototype.slice.call(arguments, 0);
70
+ window.Capybara[#{uuid.inspect}] = { from: #{method_name.to_s.inspect}, data: args };
71
+ }
72
+ JS
73
+ else
74
+ formatted_args = "[#{args.join(', ')}]"
75
+ "window.Capybara[#{uuid.inspect}] = { from: #{method_name.to_s.inspect}, data: #{formatted_args} };"
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,80 @@
1
+ require 'erb'
2
+
3
+ # Internal class for executing javascript code.
4
+ # Don't use directly
5
+ #
6
+ class Capybara::AsyncRunner::JsBuilder
7
+ # @param env [Capybara::AsyncRunner::Env] context of erb
8
+ # @param erb [String] erb template with js code
9
+ #
10
+ def initialize(env, erb)
11
+ @env = env
12
+ @erb = erb
13
+ end
14
+
15
+ attr_reader :env, :erb
16
+
17
+ # Executes provided code and returns its result
18
+ #
19
+ # @return [Object]
20
+ #
21
+ # @raise [Capybara::AsyncRunner::FailedToFetchResult] when javascript VM doesn't have any response after timeout
22
+ #
23
+ def result
24
+ Capybara.current_session.evaluate_script(calculation_code)
25
+
26
+ ResponseProcessor.new(raw_result, env.responders).result
27
+ end
28
+
29
+ private
30
+
31
+ def raw_result
32
+ RawResult.new(env.uuid).fetch or
33
+ raise Capybara::AsyncRunner::FailedToFetchResult, "No response for command with uuid = #{env.uuid}"
34
+ end
35
+
36
+ RawResult = Struct.new(:uuid) do
37
+ def fetch
38
+ Capybara::AsyncRunner::WaitHelper.wait_until(2) do
39
+ Capybara.current_session.evaluate_script(result_code)
40
+ end
41
+ end
42
+
43
+ def result_code
44
+ "window.Capybara[#{uuid.inspect}]"
45
+ end
46
+ end
47
+
48
+ ResponseProcessor = Struct.new(:raw_result, :responders) do
49
+ def method_name
50
+ raw_result['from'].to_sym
51
+ end
52
+
53
+ def data
54
+ raw_result['data'][0]
55
+ end
56
+
57
+ def responder
58
+ responders[method_name][:processor]
59
+ end
60
+
61
+ def result
62
+ responder.call(data)
63
+ end
64
+ end
65
+
66
+ def calculation_code
67
+ <<-JS
68
+ function() {
69
+ if (typeof(window.Capybara) === 'undefined') {
70
+ window.Capybara = {};
71
+ }
72
+ #{dynamic_code}
73
+ }()
74
+ JS
75
+ end
76
+
77
+ def dynamic_code
78
+ ERB.new(erb).result(env.local_binding)
79
+ end
80
+ end
@@ -0,0 +1,32 @@
1
+ # Module for storing all defined commands
2
+ #
3
+ # @example
4
+ # Capybara::AsyncRunner::Registry << SomeKlass
5
+ # Capybara::AsyncRunner::Registry[:some_name]
6
+ # # => SomeKlass
7
+ #
8
+ # You don't need to use this class directly, the gme does everything for you
9
+ #
10
+ module Capybara::AsyncRunner::Registry
11
+ # Stores provided +command_klass+ internally
12
+ #
13
+ # @param command_klass [Capybara::AsyncRunner::Command]
14
+ #
15
+ def self.<<(command_klass)
16
+ all << command_klass
17
+ end
18
+
19
+ # Returns first stored command that has provided +command_name+
20
+ #
21
+ # @paran command_name [String, Symbol]
22
+ #
23
+ def self.[](command_name)
24
+ all.detect { |klass| klass.command_name.to_s == command_name.to_s }
25
+ end
26
+
27
+ private
28
+
29
+ def self.all
30
+ @all ||= []
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ module Capybara
2
+ module AsyncRunner
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
@@ -0,0 +1,40 @@
1
+ # This module allows to run a code periodically
2
+ # and check for its returning value
3
+ #
4
+ module Capybara::AsyncRunner::WaitHelper
5
+ extend self
6
+
7
+ # Calls provided +block+ every 100ms
8
+ # and stops when it returns false
9
+ #
10
+ # @param timeout [Fixnum]
11
+ # @yield block for execution
12
+ #
13
+ # @example
14
+ # current_time = Time.now
15
+ # Capybara::AsyncRunner::WaitHelper.wait_until(3) do
16
+ # Time.now - current_time > 2
17
+ # end
18
+ #
19
+ # # 2 seconds later ...
20
+ # # => true
21
+ #
22
+ # current_time = Time.now
23
+ # Capybara::AsyncRunner::WaitHelper.wait_until(3) do
24
+ # Time.now - current_time > 10
25
+ # end
26
+ #
27
+ # # 3 seconds later (after timeout)
28
+ # # => false
29
+ #
30
+ def wait_until(timeout, &block)
31
+ begin
32
+ Timeout.timeout(timeout) do
33
+ sleep(0.1) until value = block.call
34
+ value
35
+ end
36
+ rescue TimeoutError
37
+ false
38
+ end
39
+ end
40
+ end
metadata ADDED
@@ -0,0 +1,173 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: capybara-async_runner
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ilya Bylich
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-07-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.2.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.2.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-its
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.2.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.2.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: capybara
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: poltergeist
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description:
112
+ email:
113
+ - ibylich@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".travis.yml"
120
+ - Gemfile
121
+ - LICENSE.txt
122
+ - README.md
123
+ - Rakefile
124
+ - bin/console
125
+ - bin/setup
126
+ - capybara-async_runner.gemspec
127
+ - examples/indexeddb/commands/insert.rb
128
+ - examples/indexeddb/commands/query.rb
129
+ - examples/indexeddb/commands/wrapper_initializer.rb
130
+ - examples/indexeddb/commands/wrapper_loader.rb
131
+ - examples/indexeddb/indexdb.rb
132
+ - examples/indexeddb/templates/commands/insert.js.erb
133
+ - examples/indexeddb/templates/commands/query.js.erb
134
+ - examples/indexeddb/templates/wrapper/initialize.js.erb
135
+ - examples/indexeddb/templates/wrapper/inject.js.erb
136
+ - examples/simple/command.rb
137
+ - examples/simple/template.js.erb
138
+ - lib/capybara/async_runner.rb
139
+ - lib/capybara/async_runner/command.rb
140
+ - lib/capybara/async_runner/commands/configuration.rb
141
+ - lib/capybara/async_runner/commands/responders.rb
142
+ - lib/capybara/async_runner/commands/templates.rb
143
+ - lib/capybara/async_runner/configuration.rb
144
+ - lib/capybara/async_runner/env.rb
145
+ - lib/capybara/async_runner/js_builder.rb
146
+ - lib/capybara/async_runner/registry.rb
147
+ - lib/capybara/async_runner/version.rb
148
+ - lib/capybara/async_runner/wait_helper.rb
149
+ homepage: https://github.com/iliabylich/capybara-async-runner
150
+ licenses:
151
+ - MIT
152
+ metadata: {}
153
+ post_install_message:
154
+ rdoc_options: []
155
+ require_paths:
156
+ - lib
157
+ required_ruby_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ required_rubygems_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ requirements: []
168
+ rubyforge_project:
169
+ rubygems_version: 2.4.7
170
+ signing_key:
171
+ specification_version: 4
172
+ summary: Gem for running ascynrhonous jobs in Capybara.
173
+ test_files: []