puppet7 0.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -0
- data/README +80 -0
- data/lib/puppet7/common.rb +59 -0
- data/lib/puppet7/configuration.rb +38 -0
- data/lib/puppet7/domain.rb +136 -0
- data/lib/puppet7/element.rb +62 -0
- data/lib/puppet7/exceptions.rb +17 -0
- data/lib/puppet7/extend.rb +173 -0
- data/lib/puppet7/javascript/json.js +527 -0
- data/lib/puppet7/javascript/on_page_loading.js +78 -0
- data/lib/puppet7/javascript/selenium_extension.js +5 -0
- data/lib/puppet7/page.rb +138 -0
- data/lib/puppet7/page_part.rb +140 -0
- data/lib/puppet7/puppet.rb +79 -0
- data/lib/puppet7/puppet_formatter.rb +121 -0
- data/lib/puppet7/puppet_report.rb +292 -0
- data/lib/puppet7/report/_example_group.erb +24 -0
- data/lib/puppet7/report/_examples.erb +22 -0
- data/lib/puppet7/report/puppet_report.erb +187 -0
- data/lib/puppet7/selenium_actions.rb +20 -0
- data/lib/puppet7/selenium_locator_actions.rb +302 -0
- data/lib/puppet7/selenium_support.rb +56 -0
- data/lib/puppet7/sql_agent.rb +98 -0
- data/lib/puppet7/sql_exec.rb +148 -0
- data/lib/puppet7/utils.rb +72 -0
- data/lib/puppet7.rb +28 -0
- metadata +136 -0
data/LICENSE
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
== puppet7
|
data/README
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
== puppet7
|
2
|
+
|
3
|
+
=== Introduction
|
4
|
+
Puppet7 is page object library for selenium web testing.
|
5
|
+
it's integrated RSpec2. You can write ui test code first, and then
|
6
|
+
define page objects later.
|
7
|
+
|
8
|
+
When undefined page object makes error, Puppet7 treats it pending result of rspec (yellow).
|
9
|
+
|
10
|
+
|
11
|
+
=== Quick start
|
12
|
+
|
13
|
+
You can start to write web page action and validation codes first.
|
14
|
+
|
15
|
+
require "puppet7"
|
16
|
+
describe_domain :MySite do
|
17
|
+
describe :mainPage do
|
18
|
+
it "should have search input and button" do
|
19
|
+
# all it blocks are executed in domain context
|
20
|
+
mainPage.open do # page context block
|
21
|
+
searchInput.should be_visible
|
22
|
+
searchButton.should be_visible
|
23
|
+
end
|
24
|
+
end
|
25
|
+
it "should do that you can search something." do
|
26
|
+
mainPage.open do
|
27
|
+
searchInput.type "keyword"
|
28
|
+
searchButton.click
|
29
|
+
end
|
30
|
+
searchResultPage do
|
31
|
+
should be_current
|
32
|
+
searchInput.text.should == "keyword"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
'describe_domain' is kind of a magic keyword. It'll initialize selenium session and page objects
|
39
|
+
and it make you be able to write rspec test in page object context.
|
40
|
+
|
41
|
+
Then you run this test by rspec, you can get report like below.
|
42
|
+
|
43
|
+
PENDING: 'MySite' is undefined domain!
|
44
|
+
|
45
|
+
Now you define page objects.
|
46
|
+
|
47
|
+
def_domain :MySite do
|
48
|
+
baseurl "http://my_so_cool_site.com"
|
49
|
+
page :mainPage
|
50
|
+
page :searchResultPage
|
51
|
+
end
|
52
|
+
|
53
|
+
def_page :mainPage do
|
54
|
+
url "/"
|
55
|
+
end
|
56
|
+
|
57
|
+
def_page :searchResultPage do
|
58
|
+
url "/search"
|
59
|
+
end
|
60
|
+
|
61
|
+
Run again rspec test. Maybe you can get report like below.
|
62
|
+
|
63
|
+
PENDING: 'searchInput' is undefined page action or ui element!
|
64
|
+
|
65
|
+
Ok. It's not bad. Let's define ui element in page definition.
|
66
|
+
|
67
|
+
def_page :mainPage do
|
68
|
+
url "/"
|
69
|
+
element :searchInput, ""
|
70
|
+
element :searchButton, ""
|
71
|
+
end
|
72
|
+
|
73
|
+
Run again! You can get a report.
|
74
|
+
|
75
|
+
ERROR in UI['mainPage.searchInput'] ...
|
76
|
+
|
77
|
+
Like above, you can write ui testing code step by step.
|
78
|
+
And more importantly you can write ui testing scenarios without web page.
|
79
|
+
RSpec'll generate cool document as test result, this document is a good checklist as acceptance criteria.
|
80
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# coding:utf-8
|
2
|
+
|
3
|
+
require "puppet7/puppet"
|
4
|
+
require "puppet7/exceptions"
|
5
|
+
module Puppet7
|
6
|
+
module KernelExtension
|
7
|
+
# pending method for rspec. (used internally)
|
8
|
+
def do_pending msg, ex_klass=PuppetException
|
9
|
+
if Puppet.puppet_testing
|
10
|
+
throw :pending_declared_in_example, msg
|
11
|
+
else
|
12
|
+
raise ex_klass.new msg
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# load page definitions. (relative path based current file path)
|
17
|
+
#
|
18
|
+
# example
|
19
|
+
# load_pages "pages" # load all page definitions in "./pages"
|
20
|
+
#
|
21
|
+
# def_domain :naver do
|
22
|
+
# ...
|
23
|
+
# end
|
24
|
+
def load_pages rel_path="pages"
|
25
|
+
def __loading_rubyfiles dir
|
26
|
+
Dir.new(dir).each do |filename|
|
27
|
+
next if filename == '.' or filename == '..'
|
28
|
+
file = File.join(dir, filename)
|
29
|
+
if File.directory?(file)
|
30
|
+
__loading_rubyfiles file
|
31
|
+
elsif file.end_with?(".rb")
|
32
|
+
require file
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
caller_file = caller[0].gsub /:\d+:in.*$/, ""
|
37
|
+
__loading_rubyfiles File.join(File.dirname(caller_file), rel_path)
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
# return a class given name(e.g. 'MyModule::MyClass')
|
42
|
+
def class_by_name module_name, &block
|
43
|
+
klass = Kernel
|
44
|
+
module_name.split("::").each do |name|
|
45
|
+
if klass.const_defined?(name.to_sym)
|
46
|
+
klass = klass.const_get(name.to_sym)
|
47
|
+
else
|
48
|
+
klass = nil
|
49
|
+
break
|
50
|
+
end
|
51
|
+
end
|
52
|
+
if klass and block_given?
|
53
|
+
klass.class_eval &block
|
54
|
+
end
|
55
|
+
klass
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
include Puppet7::KernelExtension
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Puppet7
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :js_ext_files, :use_report, :report_file
|
6
|
+
|
7
|
+
@@selenium_default_configs = {
|
8
|
+
:host => 'localhost',
|
9
|
+
:port => 4444,
|
10
|
+
:browser => '*firefox',
|
11
|
+
:timeout_in_seconds => 15,
|
12
|
+
:highlight_located_element => true
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@js_ext_files = []
|
17
|
+
@use_report = false
|
18
|
+
@report_file = "report.html"
|
19
|
+
@selenium_configs = @@selenium_default_configs.clone
|
20
|
+
end
|
21
|
+
|
22
|
+
def selenium_configs
|
23
|
+
@selenium_configs
|
24
|
+
end
|
25
|
+
|
26
|
+
def selenium_configs= configs
|
27
|
+
@selenium_configs.merge!(configs)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.configuration
|
32
|
+
@configuration ||= Puppet7::Configuration.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.configure
|
36
|
+
yield configuration if block_given?
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require "puppet7/puppet"
|
4
|
+
require "puppet7/page"
|
5
|
+
require "puppet7/page_part"
|
6
|
+
require "puppet7/utils"
|
7
|
+
require "puppet7/exceptions"
|
8
|
+
require "puppet7/puppet_report"
|
9
|
+
|
10
|
+
module Puppet7
|
11
|
+
class Domain < Puppet
|
12
|
+
attr_accessor :baseurl, :encoding, :pages, :selenium
|
13
|
+
include Puppet7::Utils
|
14
|
+
|
15
|
+
class << self
|
16
|
+
include Puppet7::Utils
|
17
|
+
def baseurl baseurl
|
18
|
+
self.class_eval do
|
19
|
+
@__baseurl = parse_url(baseurl).baseurl
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def encoding encoding
|
24
|
+
self.class_eval do
|
25
|
+
@__encoding = encoding
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def page page_name
|
30
|
+
self.class_eval do
|
31
|
+
@__pages ||= []
|
32
|
+
@__pages << page_name.to_sym
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def new *args
|
37
|
+
# init instance
|
38
|
+
instance = super
|
39
|
+
instance.instance_eval do
|
40
|
+
@pages = {}
|
41
|
+
end
|
42
|
+
# init instance vars.
|
43
|
+
self.class_eval do
|
44
|
+
@__baseurl ||= nil
|
45
|
+
@__encoding ||= "UTF-8"
|
46
|
+
@__pages ||= []
|
47
|
+
instance.baseurl = @__baseurl
|
48
|
+
instance.encoding = @__encoding
|
49
|
+
@__pages.each do |page_name|
|
50
|
+
begin
|
51
|
+
instance.pages[page_name.to_sym] = Page.get_instance(page_name, instance)
|
52
|
+
make_page_accessor page_name
|
53
|
+
rescue NotDefinedException
|
54
|
+
make_pending_page_accessor page_name
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
# check vars
|
59
|
+
if instance.baseurl == nil
|
60
|
+
Puppet7.report.add_undefined_object :domain, "#{name}.baseurl"
|
61
|
+
do_pending "Domain[#{instance.puppet_name}] need to define 'baseurl'!"
|
62
|
+
end
|
63
|
+
instance
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def make_page_accessor page_name
|
68
|
+
self.class_eval <<-END
|
69
|
+
def #{page_name} &block
|
70
|
+
page = pages[:'#{page_name}']
|
71
|
+
if block_given?
|
72
|
+
@block_binding = block.binding
|
73
|
+
page.instance_eval &block
|
74
|
+
@block_binding = nil
|
75
|
+
end
|
76
|
+
page
|
77
|
+
end
|
78
|
+
END
|
79
|
+
end
|
80
|
+
|
81
|
+
def make_pending_page_accessor page_name
|
82
|
+
self.class_eval <<-END
|
83
|
+
def #{page_name} &block
|
84
|
+
Puppet7.report.add_undefined_object :page, "#{page_name}"
|
85
|
+
do_pending "'#{page_name}' page is Not defined!"
|
86
|
+
end
|
87
|
+
END
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def baseurl= url
|
92
|
+
if url
|
93
|
+
@baseurl = parse_url(url).baseurl
|
94
|
+
else
|
95
|
+
@baseurl = nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def current_url_info
|
100
|
+
parse_url(selenium.location)
|
101
|
+
end
|
102
|
+
|
103
|
+
def page_title
|
104
|
+
selenium.title.force_encoding(encoding)
|
105
|
+
end
|
106
|
+
|
107
|
+
def selenium
|
108
|
+
if @selenium == nil
|
109
|
+
do_pending "Selenium is Not running!"
|
110
|
+
else
|
111
|
+
@selenium
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def selenium_init?
|
116
|
+
@selenium != nil and @selenium.session_started?
|
117
|
+
end
|
118
|
+
|
119
|
+
def method_missing sym, *args, &b
|
120
|
+
Puppet7.report.add_undefined_object :page, sym
|
121
|
+
do_pending "'#{sym}' is Not defined Domain Action or it is Not defined Page!"
|
122
|
+
end
|
123
|
+
|
124
|
+
def image_ok? img_url
|
125
|
+
selenium.run_script <<-EOF
|
126
|
+
var __img = new Image();
|
127
|
+
var __img_check_result = "'CANNOT CHECKING'";
|
128
|
+
__img.onerror= function(){__img_check_result = false;};
|
129
|
+
__img.onload= function(){__img_check_result = true;};
|
130
|
+
__img.src = '#{img_url}';
|
131
|
+
EOF
|
132
|
+
sleep 1 # wait for check img
|
133
|
+
eval(selenium.get_eval('window.__img_check_result;'))
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require "puppet7/selenium_locator_actions"
|
4
|
+
|
5
|
+
module Puppet7
|
6
|
+
class Element
|
7
|
+
include Puppet7::SeleniumLocatorActions
|
8
|
+
|
9
|
+
attr_reader :parent, :name, :nclick, :xpath
|
10
|
+
def initialize parent, name, xpath=:pending, nclick=:pending
|
11
|
+
@parent = parent
|
12
|
+
@name = name.to_sym
|
13
|
+
@xpath = xpath
|
14
|
+
end
|
15
|
+
|
16
|
+
def image_ok?
|
17
|
+
img_url = attr("src")
|
18
|
+
@parent.image_ok? img_url
|
19
|
+
end
|
20
|
+
|
21
|
+
def each
|
22
|
+
if count == 1
|
23
|
+
yield self
|
24
|
+
elsif count > 1
|
25
|
+
count.times do |idx|
|
26
|
+
yield Element.new(@parent, "#{@name}_#{idx}", _xpath_list_access(idx), @nclick)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def [] idx
|
32
|
+
Element.new(@parent, "#{@name}_#{idx}", _xpath_list_access(idx), @nclick)
|
33
|
+
end
|
34
|
+
|
35
|
+
def child rel_xpath
|
36
|
+
Element.new(@parent, "#{name}_#{rel_xpath}", "#{@xpath}#{rel_xpath}", @nclick)
|
37
|
+
end
|
38
|
+
|
39
|
+
def fullname
|
40
|
+
"#{@parent.fullname}.#{@name}"
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
private
|
45
|
+
def selenium
|
46
|
+
if @xpath == :pending
|
47
|
+
try_pending "#{fullname} has no xpath!"
|
48
|
+
end
|
49
|
+
begin
|
50
|
+
return yield(@parent.selenium)
|
51
|
+
rescue => err
|
52
|
+
raise "Error in UI[#{fullname}] : #{err}"
|
53
|
+
end if block_given?
|
54
|
+
@parent.selenium
|
55
|
+
end
|
56
|
+
|
57
|
+
def _xpath_list_access idx
|
58
|
+
xpath_list = @xpath.sub(/^xpath=/, "")
|
59
|
+
"xpath=(#{xpath_list})[#{idx +1}]"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# coding:utf-8
|
2
|
+
|
3
|
+
module Puppet7
|
4
|
+
# Exception for all puppet7 error
|
5
|
+
class PuppetException < Exception;end
|
6
|
+
|
7
|
+
# Exception for using not defined Puppets
|
8
|
+
class NotDefinedException < PuppetException ;end
|
9
|
+
|
10
|
+
# Exception for duplicated Elements.
|
11
|
+
class DuplicatedUIElementException < PuppetException ;end
|
12
|
+
class DuplicatedPagePartException < PuppetException ;end
|
13
|
+
|
14
|
+
# Exception for error while initializing selenium.
|
15
|
+
class SeleniumException < PuppetException ;end
|
16
|
+
class SeleniumInitError < SeleniumException ;end
|
17
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# coding:utf-8
|
2
|
+
|
3
|
+
require "puppet7/common"
|
4
|
+
require "puppet7/page_part"
|
5
|
+
require "puppet7/page"
|
6
|
+
require "puppet7/domain"
|
7
|
+
require "puppet7/exceptions"
|
8
|
+
|
9
|
+
module Puppet7
|
10
|
+
module KernelExtension
|
11
|
+
# Define a PagePart.
|
12
|
+
#
|
13
|
+
# Create a subclass of PagePart given name, and then evaluation given block in the class context.
|
14
|
+
#
|
15
|
+
# - example.
|
16
|
+
# def_part :header do
|
17
|
+
# element :logo, by_xpath('//*[id="logo"]')
|
18
|
+
# part :gnb
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
def def_part part_name, &block
|
22
|
+
PagePart.subclass(part_name).class_eval &block
|
23
|
+
end
|
24
|
+
|
25
|
+
# Define a Page
|
26
|
+
def def_page page_name, &block
|
27
|
+
Page.subclass(page_name.to_sym).class_eval &block if block
|
28
|
+
end
|
29
|
+
|
30
|
+
# Define a Domain
|
31
|
+
def def_domain domain_name, &block
|
32
|
+
Domain.subclass(domain_name.to_sym).class_eval &block if block
|
33
|
+
end
|
34
|
+
|
35
|
+
# initialze domain.
|
36
|
+
#
|
37
|
+
# - create domain object and pages...
|
38
|
+
# - initialize selenium session.
|
39
|
+
# - execute code block and finish domain if given block.
|
40
|
+
#
|
41
|
+
def begin_domain domain_name, &block
|
42
|
+
Puppet.begin_testing
|
43
|
+
if Domain.subclass?(domain_name)
|
44
|
+
# create domain
|
45
|
+
@domain = Domain.get_instance(domain_name)
|
46
|
+
# init selenium
|
47
|
+
begin
|
48
|
+
@domain.selenium = SeleniumSupport.init_selenium :url => @domain.baseurl
|
49
|
+
rescue SeleniumInitError => err # ignore exception
|
50
|
+
STDERR.puts "#{err}"
|
51
|
+
end
|
52
|
+
begin
|
53
|
+
yield @domain
|
54
|
+
rescue => err
|
55
|
+
STDERR.puts "#{err}"
|
56
|
+
end if block_given?
|
57
|
+
@domain
|
58
|
+
else
|
59
|
+
Puppet7.report.add_undefined_object :domain, domain_name
|
60
|
+
do_pending "Not Defined Domain : #{domain_name}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# end selenium session of domain initialized.
|
65
|
+
def end_domain
|
66
|
+
Puppet.end_testing
|
67
|
+
return unless @domain and @domain.selenium_init?
|
68
|
+
sleep 1; # delay for image error checking
|
69
|
+
|
70
|
+
# get error images
|
71
|
+
image_errors = @domain.selenium.get_stored_var :imageErrors;
|
72
|
+
image_errors.each do |page, images|
|
73
|
+
Puppet7.report.add_image_errors(page, images)
|
74
|
+
end
|
75
|
+
|
76
|
+
# get js errors
|
77
|
+
js_errors = @domain.selenium.get_stored_var :jsErrors;
|
78
|
+
js_errors.each do |page, errs|
|
79
|
+
Puppet7.report.add_js_errors(page, errs)
|
80
|
+
end
|
81
|
+
@domain.selenium.stop
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
# get database accessor
|
87
|
+
def get_dbaccessor dbi_url, user, passwd, dir
|
88
|
+
sqlexec = Puppet7::SQLExec.new dbi_url, user, passwd
|
89
|
+
caller_file = caller[0].gsub /:\d+:in.*$/, ""
|
90
|
+
sqldir = File.join(File.dirname(caller_file), dir)
|
91
|
+
Puppet7::SQLAgent.new(sqlexec, sqldir)
|
92
|
+
end
|
93
|
+
|
94
|
+
# it is shortcut for begin_domain .. end_domain.
|
95
|
+
#
|
96
|
+
# It supply domain object and domain context when you write tests with rspec.
|
97
|
+
#
|
98
|
+
# example
|
99
|
+
# describe_domain :naver do
|
100
|
+
# describe "main_page" do
|
101
|
+
# it "should have search input and search button." do
|
102
|
+
# # you could write test in domain context.
|
103
|
+
# main_page.open do
|
104
|
+
# # you could write code in page context in code block of page
|
105
|
+
# search_input.should be_visible?
|
106
|
+
# search_button.should be_visible?
|
107
|
+
# end
|
108
|
+
# end
|
109
|
+
# it "should move login page, when click login button." do
|
110
|
+
# main_page.open.login_button.click
|
111
|
+
# login_page do
|
112
|
+
# # page block checks url automatically
|
113
|
+
# # if current location is not login page, error occurrs.
|
114
|
+
# user_input.should be_visible
|
115
|
+
# passwd_input.should be_visible
|
116
|
+
# end
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
def describe_domain domain_name, *args, &block
|
121
|
+
describe "#{domain_name}", *args do
|
122
|
+
before(:each) do
|
123
|
+
begin_domain domain_name.to_sym
|
124
|
+
end
|
125
|
+
after(:each) do
|
126
|
+
end_domain
|
127
|
+
end
|
128
|
+
self.instance_exec &block
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
include Puppet7::KernelExtension
|
134
|
+
|
135
|
+
# hook method_missing of RSpec Example group.
|
136
|
+
#
|
137
|
+
# it is able to write test in domain context.
|
138
|
+
class_by_name "RSpec::Core::ExampleGroup" do
|
139
|
+
def method_missing sym, *args, &block
|
140
|
+
begin # first try super
|
141
|
+
super
|
142
|
+
rescue # hook later...
|
143
|
+
@domain.send sym, *args, &block
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# hook example
|
149
|
+
class_by_name "RSpec::Core::Example" do
|
150
|
+
alias_method :__run_before_each, :run_before_each
|
151
|
+
def run_before_each
|
152
|
+
__run_before_each
|
153
|
+
begin
|
154
|
+
desc = self.description
|
155
|
+
@example_group_instance.instance_eval do
|
156
|
+
@domain.selenium.set_stored_var :testing_message, desc
|
157
|
+
end
|
158
|
+
rescue
|
159
|
+
end if @example_group_instance.instance_eval("@domain && @domain.selenium_init?")
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class Array
|
164
|
+
def each_with *other_arrays
|
165
|
+
other_arrays.each do |arr|
|
166
|
+
raise "Error: not same array length." if arr.length != length
|
167
|
+
end
|
168
|
+
|
169
|
+
each_index do |i|
|
170
|
+
yield self[i], *(other_arrays.collect {|arr| arr[i]})
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|