lapis_lazuli 0.6.1
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 +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +42 -0
- data/LICENSE +30 -0
- data/README.md +74 -0
- data/Rakefile +1 -0
- data/bin/lapis_lazuli +3 -0
- data/lapis_lazuli.gemspec +32 -0
- data/lib/lapis_lazuli/api.rb +52 -0
- data/lib/lapis_lazuli/argparse.rb +128 -0
- data/lib/lapis_lazuli/ast.rb +160 -0
- data/lib/lapis_lazuli/browser/error.rb +93 -0
- data/lib/lapis_lazuli/browser/find.rb +500 -0
- data/lib/lapis_lazuli/browser/interaction.rb +91 -0
- data/lib/lapis_lazuli/browser/screenshots.rb +70 -0
- data/lib/lapis_lazuli/browser/wait.rb +158 -0
- data/lib/lapis_lazuli/browser.rb +246 -0
- data/lib/lapis_lazuli/cli.rb +110 -0
- data/lib/lapis_lazuli/cucumber.rb +25 -0
- data/lib/lapis_lazuli/generators/cucumber/template/.gitignore +6 -0
- data/lib/lapis_lazuli/generators/cucumber/template/Gemfile +37 -0
- data/lib/lapis_lazuli/generators/cucumber/template/README.md +27 -0
- data/lib/lapis_lazuli/generators/cucumber/template/config/config.yml +29 -0
- data/lib/lapis_lazuli/generators/cucumber/template/config/cucumber.yml +34 -0
- data/lib/lapis_lazuli/generators/cucumber/template/features/example.feature +11 -0
- data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/interaction_steps.rb +20 -0
- data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/validation_steps.rb +21 -0
- data/lib/lapis_lazuli/generators/cucumber/template/features/support/env.rb +12 -0
- data/lib/lapis_lazuli/generators/cucumber/template/features/support/transition.rb +12 -0
- data/lib/lapis_lazuli/generators/cucumber.rb +128 -0
- data/lib/lapis_lazuli/generic/xpath.rb +49 -0
- data/lib/lapis_lazuli/options.rb +28 -0
- data/lib/lapis_lazuli/placeholders.rb +36 -0
- data/lib/lapis_lazuli/proxy.rb +179 -0
- data/lib/lapis_lazuli/runtime.rb +88 -0
- data/lib/lapis_lazuli/scenario.rb +88 -0
- data/lib/lapis_lazuli/storage.rb +59 -0
- data/lib/lapis_lazuli/version.rb +10 -0
- data/lib/lapis_lazuli/versions.rb +40 -0
- data/lib/lapis_lazuli/world/annotate.rb +45 -0
- data/lib/lapis_lazuli/world/api.rb +35 -0
- data/lib/lapis_lazuli/world/browser.rb +75 -0
- data/lib/lapis_lazuli/world/config.rb +292 -0
- data/lib/lapis_lazuli/world/error.rb +141 -0
- data/lib/lapis_lazuli/world/hooks.rb +109 -0
- data/lib/lapis_lazuli/world/logging.rb +53 -0
- data/lib/lapis_lazuli/world/proxy.rb +59 -0
- data/lib/lapis_lazuli/world/variable.rb +139 -0
- data/lib/lapis_lazuli.rb +75 -0
- data/test/.gitignore +8 -0
- data/test/Gemfile +42 -0
- data/test/README.md +35 -0
- data/test/config/config.yml +37 -0
- data/test/config/cucumber.yml +37 -0
- data/test/features/annotation.feature +23 -0
- data/test/features/browser.feature +10 -0
- data/test/features/button.feature +38 -0
- data/test/features/click.feature +35 -0
- data/test/features/error.feature +30 -0
- data/test/features/find.feature +92 -0
- data/test/features/har.feature +9 -0
- data/test/features/modules.feature +14 -0
- data/test/features/step_definitions/interaction_steps.rb +154 -0
- data/test/features/step_definitions/validation_steps.rb +350 -0
- data/test/features/support/env.rb +21 -0
- data/test/features/text_field.feature +32 -0
- data/test/features/timing.feature +47 -0
- data/test/features/variable.feature +11 -0
- data/test/features/xpath.feature +41 -0
- data/test/server/start.rb +17 -0
- data/test/server/www/button.html +22 -0
- data/test/server/www/error_html.html +9 -0
- data/test/server/www/find.html +66 -0
- data/test/server/www/javascript_error.html +12 -0
- data/test/server/www/text_fields.html +15 -0
- data/test/server/www/timing.html +32 -0
- data/test/server/www/xpath.html +22 -0
- metadata +295 -0
data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/interaction_steps.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
################################################################################
|
2
|
+
# Copyright <%= config[:year] %> spriteCloud B.V. All rights reserved.
|
3
|
+
# Generated by LapisLazuli, version <%= config[:lapis_lazuli][:version] %>
|
4
|
+
# Author: "<%= config[:user] %>" <<%= config[:email] %>>
|
5
|
+
|
6
|
+
Given(/^I navigate to (.*) in (.*)$/) do |site,language|
|
7
|
+
config_name = "#{site.downcase}.#{language.downcase}"
|
8
|
+
if has_env?(config_name)
|
9
|
+
url = env(config_name)
|
10
|
+
browser.goto url
|
11
|
+
else
|
12
|
+
error(:env => config_name)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Given(/^I search for "(.*?)"$/) do |query|
|
17
|
+
searchbox = browser.find(:text_field => {:name => "q"})
|
18
|
+
searchbox.clear rescue log.debug "Could not clear searchbox"
|
19
|
+
searchbox.send_keys(query)
|
20
|
+
end
|
data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/validation_steps.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
################################################################################
|
2
|
+
# Copyright <%= config[:year] %> spriteCloud B.V. All rights reserved.
|
3
|
+
# Generated by LapisLazuli, version <%= config[:lapis_lazuli][:version] %>
|
4
|
+
# Author: "<%= config[:user] %>" <<%= config[:email] %>>
|
5
|
+
|
6
|
+
Then(/I see "([^"]*)" on the page/) do |string|
|
7
|
+
# Note: The following is *really* slow, as it'll apply the regex to all
|
8
|
+
# elements in the page, one after the other. Of course, if any element
|
9
|
+
# includes the regex, all its parent elements also will, so you have
|
10
|
+
# tons of matches to process.
|
11
|
+
#
|
12
|
+
# browser.wait(:text => /#{string}/i)
|
13
|
+
|
14
|
+
# Instead, you will want to search only the root element for some
|
15
|
+
# text, e.g.
|
16
|
+
#
|
17
|
+
# browser.wait(:html => {:text => /#{string}/i})
|
18
|
+
|
19
|
+
# There's a shortcut for that in find/wait:
|
20
|
+
browser.wait(:html => /#{string}/i)
|
21
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
################################################################################
|
2
|
+
# Copyright <%= config[:year] %> spriteCloud B.V. All rights reserved.
|
3
|
+
# Generated by LapisLazuli, version <%= config[:lapis_lazuli][:version] %>
|
4
|
+
# Author: "<%= config[:user] %>" <<%= config[:email] %>>
|
5
|
+
require 'lapis_lazuli'
|
6
|
+
require 'lapis_lazuli/cucumber'
|
7
|
+
|
8
|
+
LapisLazuli::WorldModule::Config.config_file = "config/config.yml"
|
9
|
+
World(LapisLazuli)
|
10
|
+
|
11
|
+
# Transition function from old codebase to new
|
12
|
+
load 'features/support/transition.rb'
|
@@ -0,0 +1,12 @@
|
|
1
|
+
##
|
2
|
+
# All function in this file SHOULD not be used.
|
3
|
+
# There only included for backwards compatibility
|
4
|
+
#
|
5
|
+
|
6
|
+
##
|
7
|
+
# Creating a link for easy debugging afterwards
|
8
|
+
def create_link(name, url)
|
9
|
+
log.info("[DEPRECATED] [feature/support/transition.rb] create_link")
|
10
|
+
#Lets just send the url without the parameters to prevent html display problems
|
11
|
+
"<a href='#{url}' target='_blank'>#{name}</a>"
|
12
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
#
|
2
|
+
# LapisLazuli
|
3
|
+
# https://github.com/spriteCloud/lapis-lazuli
|
4
|
+
#
|
5
|
+
# Copyright (c) 2013-2014 spriteCloud B.V. and other LapisLazuli contributors.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'lapis_lazuli/version'
|
10
|
+
|
11
|
+
require 'thor/group'
|
12
|
+
|
13
|
+
module LapisLazuli
|
14
|
+
module Generators
|
15
|
+
|
16
|
+
PROJECT_PATHS = [
|
17
|
+
'config',
|
18
|
+
'features',
|
19
|
+
File.join('features', 'step_definitions'),
|
20
|
+
File.join('features', 'support'),
|
21
|
+
'log',
|
22
|
+
'results',
|
23
|
+
'screenshots',
|
24
|
+
]
|
25
|
+
|
26
|
+
|
27
|
+
ALLOWED_HIDDEN = [
|
28
|
+
'.gitignore'
|
29
|
+
]
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
class Cucumber < Thor::Group
|
34
|
+
include Thor::Actions
|
35
|
+
|
36
|
+
argument :path, :type => :string
|
37
|
+
|
38
|
+
# Bug in Thor: this seems to need to be in place both here, and in the CLI
|
39
|
+
# class - here to actually work, and in the CLI class to be shown in the help.
|
40
|
+
class_option :branch, :aliases => "-b", :type => :string, :default => nil
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
def create_directory_structure
|
45
|
+
empty_directory(path)
|
46
|
+
PROJECT_PATHS.each do |p|
|
47
|
+
empty_directory(File.join(path, p))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
def copy_template
|
54
|
+
opts = {
|
55
|
+
:year => Time.now.year,
|
56
|
+
:user => Cucumber.get_username(self),
|
57
|
+
:email => Cucumber.get_email(self),
|
58
|
+
:lapis_lazuli => {
|
59
|
+
:version => LapisLazuli::VERSION,
|
60
|
+
:dependency => '"' + LapisLazuli::VERSION + '"',
|
61
|
+
},
|
62
|
+
:project => {
|
63
|
+
:name => File.basename(path),
|
64
|
+
},
|
65
|
+
}
|
66
|
+
|
67
|
+
# If a branch was specified on the CLI, we have to update the dependency
|
68
|
+
# string.
|
69
|
+
if options.has_key?("branch")
|
70
|
+
opts[:lapis_lazuli][:dependency] = ":github => 'spriteCloud/lapis-lazuli', :branch => '#{options["branch"]}'"
|
71
|
+
end
|
72
|
+
|
73
|
+
require 'facets/string/lchomp'
|
74
|
+
require 'find'
|
75
|
+
Find.find(Cucumber.source_root) do |name|
|
76
|
+
# Skip the source root itself
|
77
|
+
next if name == Cucumber.source_root
|
78
|
+
|
79
|
+
# Find the relative path and file name component
|
80
|
+
relative = name.lchomp(Cucumber.source_root + File::SEPARATOR)
|
81
|
+
filename = File.basename(relative)
|
82
|
+
|
83
|
+
# Ignore hidden files UNLESS they're listed in the allowed hidden
|
84
|
+
# files.
|
85
|
+
if filename.start_with?('.')
|
86
|
+
next if not ALLOWED_HIDDEN.include?(filename)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Create directories as empty directories, and treat every file as
|
90
|
+
# a template.
|
91
|
+
if File.directory?(name)
|
92
|
+
empty_directory(File.join(path, relative))
|
93
|
+
else
|
94
|
+
template(relative, File.join(path, relative), opts)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
|
101
|
+
def self.source_root
|
102
|
+
File.join(File.dirname(__FILE__), "cucumber", "template")
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
|
107
|
+
def self.run_helper(cuke, command, default)
|
108
|
+
begin
|
109
|
+
cuke.run(command, {:capture => true}).strip || default
|
110
|
+
rescue
|
111
|
+
default
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
|
117
|
+
def self.get_username(cuke)
|
118
|
+
run_helper(cuke, 'git config --get user.name', 'spriteCloud B.V.')
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
|
123
|
+
def self.get_email(cuke)
|
124
|
+
run_helper(cuke, 'git config --get user.email', 'info@spritecloud.com')
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#
|
2
|
+
# LapisLazuli
|
3
|
+
# https://github.com/spriteCloud/lapis-lazuli
|
4
|
+
#
|
5
|
+
# Copyright (c) 2013-2014 spriteCloud B.V. and other LapisLazuli contributors.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
module LapisLazuli
|
9
|
+
module GenericModule
|
10
|
+
|
11
|
+
##
|
12
|
+
# Helper functions for XPath composition
|
13
|
+
module XPath
|
14
|
+
|
15
|
+
|
16
|
+
##
|
17
|
+
# Return an xpath contains clause for e.g. checking wether an element's
|
18
|
+
# class attribute node contains a string. The optional third parameter
|
19
|
+
# determines how substrings are separated in the attribute node; the
|
20
|
+
# default is space for matching class names.
|
21
|
+
# Note that enclosing [ and ] are not included in the return value; this
|
22
|
+
# lets you more easily use and()/or()/not() operators.
|
23
|
+
def xp_contains(node, needle, separator = ' ')
|
24
|
+
contains = "contains(concat('#{separator}', normalize-space(#{node}), '#{separator}'), '#{separator}#{needle}#{separator}')"
|
25
|
+
return contains
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Constructs xpath and clause
|
30
|
+
def xp_and(first, second)
|
31
|
+
return "(#{first} and #{second})"
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Constructs xpath or clause
|
36
|
+
def xp_or(first, second)
|
37
|
+
return "(#{first} or #{second})"
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Constructs xpath or clause
|
42
|
+
def xp_not(expr)
|
43
|
+
return "not(#{expr})"
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
end # module XPath
|
48
|
+
end # module GenericModule
|
49
|
+
end # module LapisLazuli
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#
|
2
|
+
# LapisLazuli
|
3
|
+
# https://github.com/spriteCloud/lapis-lazuli
|
4
|
+
#
|
5
|
+
# Copyright (c) 2013-2014 spriteCloud B.V. and other LapisLazuli contributors.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
module LapisLazuli
|
9
|
+
##
|
10
|
+
# Configuration options and their default values
|
11
|
+
CONFIG_OPTIONS = {
|
12
|
+
"close_browser_after" => ["feature", "Close the browser after every scenario, feature, etc. Possible values are 'feature', 'scenario', 'end' and 'never'."],
|
13
|
+
"error_strings" => [nil, "List of strings that indicate errors when detected on a web page."],
|
14
|
+
"default_env" => [nil, "Indicates which environment specific configuration to load when no test environment is provided explicitly."],
|
15
|
+
"test_env" => [nil, "Indicates which environment specific configuration to load in this test run."],
|
16
|
+
"browser" => ['firefox', "Indicates the browser in which to run tests. Possible values are 'firefox', 'chrome', 'safari', 'ie', 'ios'."],
|
17
|
+
"email_domain" => ["google.com", "The domain name used when generating email addresses. See the `placeholders` command for more information."],
|
18
|
+
"screenshot_on_failure" => [true, "Toggle whether failed scenarios should result in a screenshot being taken automatically."],
|
19
|
+
"screenshot_dir" => [".#{File::SEPARATOR}screenshots", "Location prefix for the screenshot path."],
|
20
|
+
"screenshot_scheme" => ["old", "Naming scheme for screenshots. Possible values are 'old' and 'new'. This option will be deprecated in the near future, and only the new scheme will be supported."],
|
21
|
+
"breakpoint_on_error" => [false, "If the error() function is used to create errors, should the debugger be started?"],
|
22
|
+
"step_pause_time" => [0, "(Deprecated) Number of seconds to wait after each cucumber step is executed."],
|
23
|
+
"log_dir" => [".#{File::SEPARATOR}logs", "Location for log files; they'll be named like the configuration file but with the '.log' extension."],
|
24
|
+
"log_file" => [nil, "Location of log file; overrides 'log_dir'."],
|
25
|
+
"log_level" => ['DEBUG', "Log level; see ruby Logger class for details."],
|
26
|
+
"storage_dir" => [".#{File::SEPARATOR}storage", "Location prefix where to output test information file with the '.json' extension."]
|
27
|
+
}
|
28
|
+
end # module LapisLazuli
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#
|
2
|
+
# LapisLazuli
|
3
|
+
# https://github.com/spriteCloud/lapis-lazuli
|
4
|
+
#
|
5
|
+
# Copyright (c) 2013-2014 spriteCloud B.V. and other LapisLazuli contributors.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
#
|
9
|
+
module LapisLazuli
|
10
|
+
##
|
11
|
+
# Placeholders and their meanings.
|
12
|
+
# The first value is a string to be eval'd to determine the value the
|
13
|
+
# placeholder is to be replaced with.
|
14
|
+
# The second value describes the meaning.
|
15
|
+
PLACEHOLDERS = {
|
16
|
+
:timestamp => ['time[:timestamp]', 'The local time at which the test run started.'],
|
17
|
+
:iso_timestamp => ['time[:iso_timestamp]', 'The UTC time at which the test run started.'],
|
18
|
+
:iso_short => ['time[:iso_short]', 'A shorter version of the UTC time above.'],
|
19
|
+
:epoch => ['time[:epoch]', 'An integer representation of the local time above, relative to the epoch.'],
|
20
|
+
:email => ['"test_#{uuid}@#{email_domain}"', 'A unique email for the test run (contains the UUID).'],
|
21
|
+
:uuid => ['uuid', 'A UUID for the test run.'],
|
22
|
+
:scenario_id => ['scenario.id', 'A unique identifier for the current scenario based on the title, in filesystem safe form.'],
|
23
|
+
:scenario_timestamp => ['scenario.time[:timestamp]', 'Same as timestamp, but relative to the start of the scenario.'],
|
24
|
+
:scenario_iso_timestamp => ['scenario.time[:iso_timestamp]', 'Same as iso_timestamp, but relative to the start of the scenario.'],
|
25
|
+
:scenario_iso_short => ['scenario.time[:iso_short]', 'Same as iso_short, but relative to the start of the scenario.'],
|
26
|
+
:scenario_epoch => ['scenario.time[:epoch]', 'Same as epoch, but relative to the start of the scenario.'],
|
27
|
+
:scenario_email => ['"test_#{uuid}_scenario_#{scenario.uuid}@#{email_domain}"', 'Same as email, but contains the test run UUID and the scenario UUID.'],
|
28
|
+
:scenario_uuid => ['scenario.uuid', 'A UUID for the scenario.'],
|
29
|
+
:random => ['rand(9999)', 'A random integer <10,000.'],
|
30
|
+
:random_small => ['rand(99)', 'A random integer <100.'],
|
31
|
+
:random_lange => ['rand(999999)', 'A random integer <1,000,000.'],
|
32
|
+
:random_uuid => ['random_uuid', 'A random UUID.'],
|
33
|
+
:random_email => ['"test_#{uuid}_random_#{random_uuid}@#{email_domain}"', 'Same as email, but contains the test run and the random UUID.'],
|
34
|
+
:versions => ['LapisLazuli.software_versions.nil? ? "" : JSON.generate(LapisLazuli.software_versions)', 'A JSON serialized string of software versions found in e.g. the AfterConfiguration hook.']
|
35
|
+
}
|
36
|
+
end # module LapisLazuli
|
@@ -0,0 +1,179 @@
|
|
1
|
+
#
|
2
|
+
# LapisLazuli
|
3
|
+
# https://github.com/spriteCloud/lapis-lazuli
|
4
|
+
#
|
5
|
+
# Copyright (c) 2013-2014 spriteCloud B.V. and other LapisLazuli contributors.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'socket'
|
10
|
+
require 'timeout'
|
11
|
+
require "lapis_lazuli/api"
|
12
|
+
|
13
|
+
module LapisLazuli
|
14
|
+
##
|
15
|
+
# Proxy class to map to sc-proxy
|
16
|
+
class Proxy
|
17
|
+
attr_reader :is_scproxy, :api, :ip, :scproxy_port, :port
|
18
|
+
|
19
|
+
##
|
20
|
+
# Create a new LL Proxy
|
21
|
+
# What is the ip/port of the master?
|
22
|
+
def initialize(ip, port, scproxy=true)
|
23
|
+
# Save the information
|
24
|
+
@ip = ip
|
25
|
+
@is_scproxy = scproxy
|
26
|
+
if scproxy
|
27
|
+
@scproxy_port = port
|
28
|
+
else
|
29
|
+
@port = port
|
30
|
+
end
|
31
|
+
# We should have a master
|
32
|
+
if !is_port_open?(ip, port)
|
33
|
+
raise "Proxy not online"
|
34
|
+
end
|
35
|
+
if @is_scproxy
|
36
|
+
# Create an API connection to the master
|
37
|
+
@api = API.new()
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def has_session?()
|
42
|
+
return !@port.nil? && is_port_open?(@ip, @port);
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Creates a new session with the proxy
|
47
|
+
def create()
|
48
|
+
# Do we already have a connection?
|
49
|
+
if @is_scproxy and self.has_session?
|
50
|
+
# Close it before starting a new one
|
51
|
+
self.close()
|
52
|
+
end
|
53
|
+
# Create a new
|
54
|
+
if @is_scproxy and @api
|
55
|
+
# Let the master create a new proxy
|
56
|
+
response = self.proxy_new :master => true
|
57
|
+
# Did we get on?
|
58
|
+
if response["status"] == true
|
59
|
+
@port = response["result"]["port"]
|
60
|
+
else
|
61
|
+
# Show the error
|
62
|
+
raise response["message"]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
if @port.nil?
|
67
|
+
raise "Coult not create a new proxy"
|
68
|
+
end
|
69
|
+
|
70
|
+
return @port
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Close the session with the proxy
|
75
|
+
def close()
|
76
|
+
# If we don't have one we don't do anything
|
77
|
+
return if !@is_scproxy or !self.has_session?
|
78
|
+
|
79
|
+
# Send the call to the master
|
80
|
+
response = self.proxy_close :port => @port, :master => true
|
81
|
+
|
82
|
+
# Did we close it?
|
83
|
+
if response["status"] == true
|
84
|
+
# Clear our session
|
85
|
+
@port = nil
|
86
|
+
else
|
87
|
+
# Show an error
|
88
|
+
raise response["message"]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Check if a TCP port is open on a host
|
94
|
+
def is_port_open?(ip, port)
|
95
|
+
begin
|
96
|
+
# Timeout is important
|
97
|
+
Timeout::timeout(1) do
|
98
|
+
begin
|
99
|
+
# Create the socket and close it
|
100
|
+
s = TCPSocket.new(ip, port)
|
101
|
+
s.close
|
102
|
+
return true
|
103
|
+
# If it fails the port is closed
|
104
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
105
|
+
end
|
106
|
+
end
|
107
|
+
rescue Timeout::Error
|
108
|
+
end
|
109
|
+
|
110
|
+
# Sorry port is closed
|
111
|
+
return false
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Map any missing method to the API object
|
116
|
+
#
|
117
|
+
# Example
|
118
|
+
# proxy.har_get
|
119
|
+
# proxy.proxy_close :port => 10002
|
120
|
+
def method_missing(meth, *args, &block)
|
121
|
+
# Only for spritecloud proxies
|
122
|
+
if !@is_scproxy
|
123
|
+
raise "Incorrect method: #{meth}"
|
124
|
+
end
|
125
|
+
|
126
|
+
# We should have no arguments or a Hash
|
127
|
+
if args.length > 1 or (args.length == 1 and not args[0].is_a? Hash)
|
128
|
+
raise "Incorrect arguments: #{args}"
|
129
|
+
end
|
130
|
+
settings = args[0] || {}
|
131
|
+
|
132
|
+
# A custom block or arguments?
|
133
|
+
block = block_given? ? block : Proc.new do |req|
|
134
|
+
if args.length == 1
|
135
|
+
settings.each do |key,value|
|
136
|
+
req.params[key.to_s] = value.to_s
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Pick the master proxy or the proxy for this session
|
142
|
+
@api.set_conn("http://#{@ip}:#{(settings.has_key? :master) ? @scproxy_port : @port}/")
|
143
|
+
|
144
|
+
# Call the API
|
145
|
+
response = @api.get("/#{meth.to_s.gsub("_","/")}", nil, &block)
|
146
|
+
# Only return the body if we could parse the JSOn
|
147
|
+
if response.body.is_a? Hash
|
148
|
+
return response.body
|
149
|
+
else
|
150
|
+
# Got a serious issue here, label as code 500
|
151
|
+
return {
|
152
|
+
"code" => 500,
|
153
|
+
"status" => false,
|
154
|
+
"message" => "Incorrect response from proxy",
|
155
|
+
"result" => response
|
156
|
+
}
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
##
|
161
|
+
# During the end of the test run all data should be added to the storage
|
162
|
+
def destroy(world)
|
163
|
+
begin
|
164
|
+
# Is it a spriteCloud proxy?
|
165
|
+
if @is_scproxy
|
166
|
+
# Request HAR data
|
167
|
+
response = self.har_get
|
168
|
+
if response["status"] == true
|
169
|
+
# Add it to the storage
|
170
|
+
world.storage.set("har", response["result"])
|
171
|
+
end
|
172
|
+
end
|
173
|
+
self.close
|
174
|
+
rescue StandardError => err
|
175
|
+
world.log.debug("Failed to close the proxy: #{err}")
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
#
|
2
|
+
# LapisLazuli
|
3
|
+
# https://github.com/spriteCloud/lapis-lazuli
|
4
|
+
#
|
5
|
+
# Copyright (c) 2013-2014 spriteCloud B.V. and other LapisLazuli contributors.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
require 'singleton'
|
9
|
+
|
10
|
+
module LapisLazuli
|
11
|
+
|
12
|
+
##
|
13
|
+
# Simple singleton class (that therefore lives for the duration of the test's
|
14
|
+
# run time for managing objects whose lifetime should also be this long.
|
15
|
+
class Runtime
|
16
|
+
include Singleton
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@objects = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def has?(name)
|
23
|
+
return @objects.has_key? name
|
24
|
+
end
|
25
|
+
|
26
|
+
def set(world, name, object, destructor = nil)
|
27
|
+
if @objects.has_key? name
|
28
|
+
Runtime.destroy(world, name, destructor)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Register a finalizer, so we can clean up the proxy again
|
32
|
+
ObjectSpace.define_finalizer(self, Runtime.destroy(world, name, destructor))
|
33
|
+
|
34
|
+
@objects[name] = object
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_if(world, name, destructor = nil, &block)
|
38
|
+
if @objects.has_key? name
|
39
|
+
return @objects[name]
|
40
|
+
end
|
41
|
+
|
42
|
+
obj = block.call
|
43
|
+
|
44
|
+
set(world, name, obj, destructor)
|
45
|
+
|
46
|
+
return obj
|
47
|
+
end
|
48
|
+
|
49
|
+
def get(name)
|
50
|
+
return @objects[name]
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
private
|
55
|
+
def self.destroy(world, name, destructor)
|
56
|
+
Proc.new do
|
57
|
+
# If a destructor is given, call that.
|
58
|
+
if not destructor.nil?
|
59
|
+
return destructor.call(world)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Next, try a has_foo?/foo.destroy combination
|
63
|
+
if world.respond_to? "has_#{name}?" and world.respond_to? name
|
64
|
+
if world.send("has_#{name}?")
|
65
|
+
return world.send(name).destroy(world)
|
66
|
+
end
|
67
|
+
return false
|
68
|
+
end
|
69
|
+
|
70
|
+
# If it only responds to a destroy function, then we can just
|
71
|
+
# call that.
|
72
|
+
if world.respond_to? name
|
73
|
+
return world.send(name).destroy(world)
|
74
|
+
end
|
75
|
+
|
76
|
+
# If all else fails, we have to log an error. We can't rely
|
77
|
+
# on log existing in world, though...
|
78
|
+
message = "No destructor available for #{name}."
|
79
|
+
if world.respond_to? :log
|
80
|
+
world.log.info(message)
|
81
|
+
else
|
82
|
+
puts message
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end # class Runtime
|
88
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
#
|
2
|
+
# LapisLazuli
|
3
|
+
# https://github.com/spriteCloud/lapis-lazuli
|
4
|
+
#
|
5
|
+
# Copyright (c) 2013-2014 spriteCloud B.V. and other LapisLazuli contributors.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
require "securerandom"
|
9
|
+
require "lapis_lazuli/storage"
|
10
|
+
require "lapis_lazuli/ast"
|
11
|
+
|
12
|
+
module LapisLazuli
|
13
|
+
##
|
14
|
+
# Stores the Cucumber scenario
|
15
|
+
# Includes timing, running state and a name
|
16
|
+
class Scenario
|
17
|
+
include LapisLazuli::Ast
|
18
|
+
|
19
|
+
attr_reader :id, :time, :uuid, :data, :storage, :error
|
20
|
+
attr_accessor :running, :check_browser_errors
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@uuid = SecureRandom.hex
|
24
|
+
@storage = Storage.new
|
25
|
+
@running = false
|
26
|
+
@name = "start_of_test_run"
|
27
|
+
self.update_timestamp
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Update the scenario with a new one
|
32
|
+
def update(scenario)
|
33
|
+
@uuid = SecureRandom.hex
|
34
|
+
# Reset the fail attribute
|
35
|
+
@check_browser_errors = true
|
36
|
+
# The original scenario from cucumber
|
37
|
+
@data = scenario
|
38
|
+
# A name without special characters.
|
39
|
+
@id = clean(scenario_id(scenario))
|
40
|
+
self.update_timestamp
|
41
|
+
end
|
42
|
+
|
43
|
+
def update_timestamp
|
44
|
+
now = Time.now
|
45
|
+
# The current time
|
46
|
+
@time = {
|
47
|
+
:timestamp => now.strftime('%y%m%d_%H%M%S'),
|
48
|
+
:iso_timestamp => now.utc.strftime("%FT%TZ"),
|
49
|
+
:iso_short => now.utc.strftime("%y%m%dT%H%M%SZ"),
|
50
|
+
:epoch => now.to_i.to_s
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def tags
|
55
|
+
if !@data.nil?
|
56
|
+
return @data.source_tag_names
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def scope(cleaned = false)
|
61
|
+
scope = nil
|
62
|
+
if @data.respond_to? :backtrace_line
|
63
|
+
scope = @data.backtrace_line
|
64
|
+
elsif @data.respond_to? :file_colon_line
|
65
|
+
scope = @data.file_colon_line
|
66
|
+
end
|
67
|
+
|
68
|
+
if scope.nil?
|
69
|
+
return nil
|
70
|
+
elsif cleaned
|
71
|
+
return clean [scope]
|
72
|
+
else
|
73
|
+
return scope
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def clean(strings)
|
80
|
+
result = []
|
81
|
+
strings.each do |string|
|
82
|
+
clean_string = string.gsub(/[^\w\.\-]/, ' ').strip.squeeze(' ').gsub(" ","_")
|
83
|
+
result.push(clean_string)
|
84
|
+
end
|
85
|
+
return result.join("-").squeeze("-")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|