citronella 0.0.3 → 0.0.4
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 +4 -4
- data/README.md +158 -1
- data/citronella.gemspec +3 -3
- data/lib/citronella.rb +24 -0
- data/lib/logger.rb +35 -0
- data/lib/page_decorator.rb +46 -0
- data/lib/page_tab.rb +51 -0
- data/lib/placeholder_page.rb +29 -0
- data/lib/ui.rb +40 -2
- data/lib/web_page.rb +121 -16
- data/lib/web_ui.rb +93 -0
- metadata +9 -7
- data/lib/page_store.rb +0 -17
- data/lib/page_wrapper.rb +0 -30
- data/lib/ui_object.rb +0 -43
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9020c7d6488bbbd5e67e35a1c939a2af5296736bbe99c45a6d8194ff347676aa
|
|
4
|
+
data.tar.gz: 1fb3f63ea173a9c6ca720bfa945037bb8f6a595922929ee91972751e82d0239d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9c52cdaeca3173c7d224f2d5f390b83d8b5e9835937bf1af3fa2e8a9ad05c03a1f16f02357b7a85f8052f36c598763a8b9a1551a8606964b78f53dee4bbd1194
|
|
7
|
+
data.tar.gz: cfd8f9876408c022b04eab006ac24793473ee5a946bba6963446e2d7a66736e917def508b0528718ae7f6003b40da63dd43be16653d60128798a81aea3068165
|
data/README.md
CHANGED
|
@@ -1,6 +1,163 @@
|
|
|
1
1
|
# Citronella
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](http://badge.fury.io/rb/citronella)
|
|
4
|
+
|
|
5
|
+
webdriver extension with a page object wrapper.
|
|
6
|
+
|
|
7
|
+
## Example Tests
|
|
8
|
+
```ruby
|
|
9
|
+
require 'test/unit'
|
|
10
|
+
require "selenium-webdriver"
|
|
11
|
+
require 'citronella'
|
|
12
|
+
require_relative '../pages/contents_page'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class NavigationTest < Test::Unit::TestCase
|
|
16
|
+
def setup
|
|
17
|
+
options = Selenium::WebDriver::Chrome::Options.new
|
|
18
|
+
driver = Selenium::WebDriver.for :chrome, options: options
|
|
19
|
+
@web = Citronella::Web::WebPage.new(driver)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def teardown
|
|
23
|
+
@web.driver.quit
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_navigation
|
|
27
|
+
@web.page_object(ContentsPage.new.home_page, url=true)
|
|
28
|
+
@web.page.releases_button.click
|
|
29
|
+
assert_includes(@web.driver.title, 'Releases')
|
|
30
|
+
|
|
31
|
+
@web.page.gems_button.click
|
|
32
|
+
assert_includes(@web.driver.title, 'Gem')
|
|
33
|
+
|
|
34
|
+
@web.page.sign_in_button.click
|
|
35
|
+
assert_includes(@web.driver.title, 'Sign in')
|
|
36
|
+
|
|
37
|
+
@web.page.sign_up_button.click
|
|
38
|
+
assert_includes(@web.driver.title, 'Sign up')
|
|
39
|
+
|
|
40
|
+
@web.page.guides_button.click
|
|
41
|
+
assert_includes(@web.driver.title, 'Guides')
|
|
42
|
+
|
|
43
|
+
@web.page.blog_button.click
|
|
44
|
+
assert_includes(@web.driver.title, 'Blog')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
Even though this module is mainly designed for the page object model, it can also be used without it for quick prototyping or mockups, etc.
|
|
49
|
+
```ruby
|
|
50
|
+
require 'test/unit'
|
|
51
|
+
require "selenium-webdriver"
|
|
52
|
+
require 'citronella'
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class PackageSearchTest < Test::Unit::TestCase
|
|
56
|
+
def setup
|
|
57
|
+
options = Selenium::WebDriver::Chrome::Options.new
|
|
58
|
+
driver = Selenium::WebDriver.for :chrome, options: options
|
|
59
|
+
@web = Citronella::Web::WebPage.new(driver)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def teardown
|
|
63
|
+
@web.driver.quit
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_search_package
|
|
67
|
+
@web.driver.navigate.to "https://rubygems.org/"
|
|
68
|
+
@web.locate(id: 'home_query').get_element.send_keys('citronella')
|
|
69
|
+
@web.locate(class: 'home__search__icon').get_element.click
|
|
70
|
+
assert(@web.locate(class: 'gems__gem__name').get_element.text, 'citronella')
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
```
|
|
74
|
+
___
|
|
75
|
+
## Install Package
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
gem install citronella
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
___
|
|
82
|
+
## Documentation
|
|
83
|
+
|
|
84
|
+
There are only three modules imported in this package:
|
|
85
|
+
|
|
86
|
+
* The first module is for the tests.
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
require 'test/unit'
|
|
90
|
+
require "selenium-webdriver"
|
|
91
|
+
require 'citronella'
|
|
92
|
+
|
|
93
|
+
class NavigationTest < Test::Unit::TestCase
|
|
94
|
+
def setup
|
|
95
|
+
options = Selenium::WebDriver::Chrome::Options.new
|
|
96
|
+
driver = Selenium::WebDriver.for :chrome, options: options
|
|
97
|
+
@web = Citronella::Web::WebPage.new(driver)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def teardown
|
|
101
|
+
@web.driver.quit
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
* The second and third modules are for the page object model.
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
require 'citronella'
|
|
110
|
+
require_relative '../contents_page'
|
|
111
|
+
|
|
112
|
+
class HomePage < ContentsPage.new.header_menu
|
|
113
|
+
@url = "https://rubygems.org/"
|
|
114
|
+
|
|
115
|
+
def search_button
|
|
116
|
+
ui(class: 'home__search__icon', page: ContentsPage.new.search_page)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def code_link_button
|
|
120
|
+
ui(css: 'div.nav--v > a:nth-child(3)', page: Citronella::Dummy::PlaceholderPage)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
___
|
|
126
|
+
## Usage
|
|
127
|
+
|
|
128
|
+
### citronella.WebPage
|
|
129
|
+
|
|
130
|
+
###### Args:
|
|
131
|
+
- driver / webdriver
|
|
132
|
+
|
|
133
|
+
###### Kwargs (optional):
|
|
134
|
+
- webdriver_wait `number(seconds)`, default value is `10`
|
|
135
|
+
- logger `bool`, default value is `true`
|
|
136
|
+
|
|
137
|
+
###### Method Lists:
|
|
138
|
+
| Method Name | Args* | Kwargs** | Note |
|
|
139
|
+
| ------------------ |:-----------:|:----------------:|:----:|
|
|
140
|
+
| driver | None | None | return selenium `webdriver` object |
|
|
141
|
+
| locate | None | how: what | similar as`driver.get_element` args |
|
|
142
|
+
| page_object | Page Object | url `bool` | Page Object must contain `@url` variable with if using Kwargs** |
|
|
143
|
+
| page | None | None | |
|
|
144
|
+
| back | None | None | |
|
|
145
|
+
| webdriver_wait | number(sec) | None | |
|
|
146
|
+
| ready_state | number(sec) | None | execute javascript `document.readyState` manually |
|
|
147
|
+
|
|
148
|
+
### citronella.ui / citronella.WebUi
|
|
149
|
+
|
|
150
|
+
###### Kwargs:
|
|
151
|
+
- how: what
|
|
152
|
+
- page_object (optional)
|
|
153
|
+
|
|
154
|
+
###### Method Lists:
|
|
155
|
+
| Method Name | Args* | Kwargs** | Note |
|
|
156
|
+
| ------------- |:------:|:------------------:|:----:|
|
|
157
|
+
| send_keys | text | clear `bool`, return_key `bool`, switch_page `bool` | |
|
|
158
|
+
| click | None | switch_page `bool` | |
|
|
159
|
+
| get_element | None | None | |
|
|
160
|
+
| get_elements | None | None | |
|
|
4
161
|
|
|
5
162
|
|
|
6
163
|
## Testing powered by
|
data/citronella.gemspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Gem::Specification.new do |s|
|
|
2
2
|
s.name = 'citronella'
|
|
3
|
-
s.version = '0.0.
|
|
3
|
+
s.version = '0.0.4'
|
|
4
4
|
|
|
5
5
|
s.authors = ['heyclore']
|
|
6
6
|
s.email = 'cloore@gmail.com'
|
|
@@ -10,9 +10,9 @@ Gem::Specification.new do |s|
|
|
|
10
10
|
Webdriver extension with a page object wrapper
|
|
11
11
|
DESCRIPTION
|
|
12
12
|
|
|
13
|
-
s.licenses = '
|
|
13
|
+
s.licenses = 'MIT'
|
|
14
14
|
s.required_ruby_version = '>= 2.7.0'
|
|
15
|
-
s.homepage = 'https://github.com/heyclore/citronella#readme'
|
|
15
|
+
s.homepage = 'https://github.com/heyclore/citronella/tree/main/ruby#readme'
|
|
16
16
|
s.metadata = {
|
|
17
17
|
'source_code_uri' => 'https://github.com/heyclore/citronella/tree/main/ruby',
|
|
18
18
|
'github_repo' => 'https://github.com/heyclore/citronella',
|
data/lib/citronella.rb
CHANGED
|
@@ -1,2 +1,26 @@
|
|
|
1
|
+
#MIT License
|
|
2
|
+
#
|
|
3
|
+
#Copyright (c) 2023 Eko Purnomo
|
|
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 all
|
|
13
|
+
#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 THE
|
|
21
|
+
#SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
|
1
24
|
require_relative 'web_page'
|
|
2
25
|
require_relative 'ui'
|
|
26
|
+
require_relative 'placeholder_page'
|
data/lib/logger.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#MIT License
|
|
2
|
+
#
|
|
3
|
+
#Copyright (c) 2023 Eko Purnomo
|
|
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 all
|
|
13
|
+
#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 THE
|
|
21
|
+
#SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
require 'logger'
|
|
25
|
+
|
|
26
|
+
module Citronella
|
|
27
|
+
module Log
|
|
28
|
+
def self.logger(logger, class_name, function_name, name)
|
|
29
|
+
"""This is a logger method."""
|
|
30
|
+
if logger
|
|
31
|
+
Logger.new(STDOUT, level: Logger::INFO).info("#{class_name} => #{function_name} => #{name}")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#MIT License
|
|
2
|
+
#
|
|
3
|
+
#Copyright (c) 2023 Eko Purnomo
|
|
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 all
|
|
13
|
+
#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 THE
|
|
21
|
+
#SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
require_relative 'web_ui.rb'
|
|
25
|
+
|
|
26
|
+
module Citronella
|
|
27
|
+
module Wrapper
|
|
28
|
+
class PageDecorator
|
|
29
|
+
"""This is a page decorator class."""
|
|
30
|
+
def initialize(driver, webdriver_wait, pages, logger)
|
|
31
|
+
@driver = driver
|
|
32
|
+
@webdriver_wait = webdriver_wait
|
|
33
|
+
@pages = pages
|
|
34
|
+
@logger = logger
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def method_missing(attr)
|
|
38
|
+
"""look up the attr / method name inside page object."""
|
|
39
|
+
original_method = @pages.current_page.new.method(attr)
|
|
40
|
+
args = original_method.call
|
|
41
|
+
Citronella::Ui::WebUi.new(@driver, @webdriver_wait, @pages, @logger,
|
|
42
|
+
args.last, args.first, attr, @pages.current_page.name)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/page_tab.rb
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#MIT License
|
|
2
|
+
#
|
|
3
|
+
#Copyright (c) 2023 Eko Purnomo
|
|
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 all
|
|
13
|
+
#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 THE
|
|
21
|
+
#SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
module Citronella
|
|
25
|
+
class PagesList
|
|
26
|
+
"""This is a page page tab class."""
|
|
27
|
+
def initialize
|
|
28
|
+
@pages = []
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def current_page
|
|
32
|
+
"""return the last page object stored."""
|
|
33
|
+
@pages.last
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def append(new_page)
|
|
37
|
+
"""store the page object in _pages."""
|
|
38
|
+
@pages << new_page
|
|
39
|
+
if @pages.length > 5
|
|
40
|
+
@pages.shift
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def pop
|
|
45
|
+
"""delete the last item of the _pages lists."""
|
|
46
|
+
return if @pages.empty?
|
|
47
|
+
@pages.delete_at(-1)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#MIT License
|
|
2
|
+
#
|
|
3
|
+
#Copyright (c) 2023 Eko Purnomo
|
|
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 all
|
|
13
|
+
#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 THE
|
|
21
|
+
#SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
module Citronella
|
|
25
|
+
module Dummy
|
|
26
|
+
class PlaceholderPage
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/ui.rb
CHANGED
|
@@ -1,4 +1,42 @@
|
|
|
1
|
+
#MIT License
|
|
2
|
+
#
|
|
3
|
+
#Copyright (c) 2023 Eko Purnomo
|
|
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 all
|
|
13
|
+
#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 THE
|
|
21
|
+
#SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
|
1
24
|
def ui(args)
|
|
2
|
-
|
|
3
|
-
|
|
25
|
+
"""
|
|
26
|
+
forward the data to page decorator and wrap into WebUi class.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
by
|
|
30
|
+
|
|
31
|
+
Kwarg:
|
|
32
|
+
page(optional)
|
|
33
|
+
|
|
34
|
+
Usage:
|
|
35
|
+
without reference / page object. ui(name: 'q')
|
|
36
|
+
with reference / page object. ui(id: 'submit', UserMenuPage)
|
|
37
|
+
|
|
38
|
+
page are reference for the next page object if the element redirect to
|
|
39
|
+
another page with WebUi click and send_keys(enter key) from input form.
|
|
40
|
+
"""
|
|
41
|
+
return args.delete(:page), args
|
|
4
42
|
end
|
data/lib/web_page.rb
CHANGED
|
@@ -1,24 +1,129 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
#MIT License
|
|
2
|
+
#
|
|
3
|
+
#Copyright (c) 2023 Eko Purnomo
|
|
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 all
|
|
13
|
+
#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 THE
|
|
21
|
+
#SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
require_relative 'page_tab'
|
|
25
|
+
require_relative 'page_decorator'
|
|
26
|
+
require_relative 'web_ui'
|
|
3
27
|
|
|
4
28
|
module Citronella
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
29
|
+
module Web
|
|
30
|
+
class WebPage
|
|
31
|
+
"""
|
|
32
|
+
an object class that use across the tests.
|
|
33
|
+
webdriver_wait is set '10' seconds by default
|
|
34
|
+
logger is set 'True' by default
|
|
11
35
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
36
|
+
Args:
|
|
37
|
+
driver
|
|
38
|
+
Kwargs (optional):
|
|
39
|
+
webdriver_wait
|
|
40
|
+
logger
|
|
15
41
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
42
|
+
Usage:
|
|
43
|
+
driver = Selenium::WebDriver.for :chrome
|
|
44
|
+
web = WebPage(driver)
|
|
45
|
+
"""
|
|
46
|
+
def initialize(driver, webdriver_wait:10, logger:true)
|
|
47
|
+
@driver = driver
|
|
48
|
+
@webdriver_wait = webdriver_wait
|
|
49
|
+
@pages = Citronella::PagesList.new
|
|
50
|
+
@logger = logger
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def driver
|
|
54
|
+
"""return the original selenium / appium driver."""
|
|
55
|
+
@driver
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def page_object(new_page, url=false)
|
|
59
|
+
"""
|
|
60
|
+
initialize page object module object, url kwargs is optional and
|
|
61
|
+
it set to FALSE by default, it can be use if the page object have
|
|
62
|
+
an @url variable.
|
|
63
|
+
in selenium:
|
|
64
|
+
it's equal as self.driver.navigate.to(url)
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
page_object_model
|
|
68
|
+
|
|
69
|
+
Kwargs:
|
|
70
|
+
url=true
|
|
71
|
+
|
|
72
|
+
Usage:
|
|
73
|
+
self.browser.page_object(Homepage)
|
|
74
|
+
or
|
|
75
|
+
self.browser.page_object(Homepage, url=true)
|
|
76
|
+
"""
|
|
77
|
+
@pages.append(new_page)
|
|
78
|
+
if url
|
|
79
|
+
if not new_page.instance_variable_get(:@url)
|
|
80
|
+
raise "Error: '@url' variable does not exist in #{new_page}"
|
|
81
|
+
end
|
|
82
|
+
@driver.navigate.to(new_page.instance_variable_get(:@url))
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def page
|
|
87
|
+
"""return last page object model."""
|
|
88
|
+
Citronella::Wrapper::PageDecorator.new(@driver, @webdriver_wait, @pages, @logger)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def locate(args)
|
|
92
|
+
"""
|
|
93
|
+
an alternative way for testing without page object and return WebUi
|
|
94
|
+
class, but can't use page and back method and causing an error.
|
|
95
|
+
good for quick prototype / write a tests.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
by
|
|
99
|
+
value
|
|
100
|
+
|
|
101
|
+
Usage:
|
|
102
|
+
web.ui(name: 'q').get_element.text
|
|
103
|
+
web.ui(name: 'q').get_element.click
|
|
104
|
+
"""
|
|
105
|
+
Citronella::Ui::WebUi.new(@driver, @webdriver_wait, @pages, @logger, args, nil,
|
|
106
|
+
__method__.to_s, self.class.name.split('::').last.to_s)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def back
|
|
110
|
+
"""return to previous page and delete the last page object."""
|
|
111
|
+
@driver.navigate.back
|
|
112
|
+
@pages.pop
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def ready_state(wait)
|
|
116
|
+
"""execute javascript for page to fully load"""
|
|
117
|
+
wait.times do |i|
|
|
118
|
+
return if driver.execute_script("return document.readyState") == "complete"
|
|
119
|
+
sleep(1)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
19
122
|
|
|
20
|
-
|
|
21
|
-
|
|
123
|
+
def webdriver_wait(wait)
|
|
124
|
+
"""override webdriver wait."""
|
|
125
|
+
@webdriver_wait = wait
|
|
126
|
+
end
|
|
22
127
|
end
|
|
23
128
|
end
|
|
24
129
|
end
|
data/lib/web_ui.rb
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#MIT License
|
|
2
|
+
#
|
|
3
|
+
#Copyright (c) 2023 Eko Purnomo
|
|
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 all
|
|
13
|
+
#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 THE
|
|
21
|
+
#SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
require_relative 'logger'
|
|
25
|
+
|
|
26
|
+
module Citronella
|
|
27
|
+
module Ui
|
|
28
|
+
class WebUi
|
|
29
|
+
"""a wrapped object of a web element."""
|
|
30
|
+
def initialize(driver, webdriver_wait, pages, logger, locator, new_page,
|
|
31
|
+
function_name, class_name)
|
|
32
|
+
@driver = driver
|
|
33
|
+
@wait = webdriver_wait
|
|
34
|
+
@pages = pages
|
|
35
|
+
@logger = logger
|
|
36
|
+
@locator = locator
|
|
37
|
+
@new_page = new_page
|
|
38
|
+
@function_name = function_name
|
|
39
|
+
@class_name = class_name
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private def webdriver_wait(ele, displayed=false)
|
|
43
|
+
"""return a web element or elements."""
|
|
44
|
+
el = Selenium::WebDriver::Wait.new(timeout: @wait).until { ele }
|
|
45
|
+
if displayed
|
|
46
|
+
@wait.times do
|
|
47
|
+
break if el.displayed?
|
|
48
|
+
sleep(1)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
el
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def send_keys(text, clear=false, return_key=false, switch_page=true)
|
|
55
|
+
"""custom webdriver send_keys with optional clear field."""
|
|
56
|
+
Citronella::Log.logger(@logger, @class_name, @function_name, __method__)
|
|
57
|
+
el = webdriver_wait(@driver.find_element(@locator), displayed=true)
|
|
58
|
+
el.send_keys text
|
|
59
|
+
|
|
60
|
+
if return_key
|
|
61
|
+
el.send_keys :return
|
|
62
|
+
|
|
63
|
+
if @new_page and switch_page
|
|
64
|
+
@pages.append(@new_page)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def click(switch_page=true)
|
|
70
|
+
"""click to web element."""
|
|
71
|
+
Citronella::Log.logger(@logger, @class_name, @function_name, __method__)
|
|
72
|
+
el = webdriver_wait(@driver.find_element(@locator), displayed=true)
|
|
73
|
+
el.click
|
|
74
|
+
|
|
75
|
+
if @new_page and switch_page
|
|
76
|
+
@pages.append(@new_page)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def get_element
|
|
81
|
+
"""return web element, equal as find_element."""
|
|
82
|
+
Citronella::Log.logger(@logger, @class_name, @function_name, __method__.to_s)
|
|
83
|
+
webdriver_wait(@driver.find_element(@locator))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def get_elements
|
|
87
|
+
"""return list of web element, equal as find_elements."""
|
|
88
|
+
Citronella::Log.logger(@logger, @class_name, @function_name, __method__.to_s)
|
|
89
|
+
webdriver_wait(@driver.find_elements(@locator))
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: citronella
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- heyclore
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2023-
|
|
11
|
+
date: 2023-03-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: selenium-webdriver
|
|
@@ -34,14 +34,16 @@ files:
|
|
|
34
34
|
- README.md
|
|
35
35
|
- citronella.gemspec
|
|
36
36
|
- lib/citronella.rb
|
|
37
|
-
- lib/
|
|
38
|
-
- lib/
|
|
37
|
+
- lib/logger.rb
|
|
38
|
+
- lib/page_decorator.rb
|
|
39
|
+
- lib/page_tab.rb
|
|
40
|
+
- lib/placeholder_page.rb
|
|
39
41
|
- lib/ui.rb
|
|
40
|
-
- lib/ui_object.rb
|
|
41
42
|
- lib/web_page.rb
|
|
42
|
-
|
|
43
|
+
- lib/web_ui.rb
|
|
44
|
+
homepage: https://github.com/heyclore/citronella/tree/main/ruby#readme
|
|
43
45
|
licenses:
|
|
44
|
-
-
|
|
46
|
+
- MIT
|
|
45
47
|
metadata:
|
|
46
48
|
source_code_uri: https://github.com/heyclore/citronella/tree/main/ruby
|
|
47
49
|
github_repo: https://github.com/heyclore/citronella
|
data/lib/page_store.rb
DELETED
data/lib/page_wrapper.rb
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
require_relative 'ui_object.rb'
|
|
2
|
-
|
|
3
|
-
module Citronella
|
|
4
|
-
module PageWrapper
|
|
5
|
-
def self.method_wrapper(klass, method_name, driver, webdriver_wait, pages)
|
|
6
|
-
original_method = klass.instance_method(method_name)
|
|
7
|
-
klass.define_method(method_name) do
|
|
8
|
-
puts method_name
|
|
9
|
-
args = original_method.bind(self).call
|
|
10
|
-
Citronella::UiObject::Ui.new(driver, webdriver_wait, pages,
|
|
11
|
-
args.locator, args.page, args.exception)
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def self.class_decorator(klass, driver, webdriver_wait, pages)
|
|
16
|
-
return if klass.instance_variable_defined?(:@decorated)
|
|
17
|
-
klass.instance_variable_set(:@decorated, true)
|
|
18
|
-
return if klass.name == "Object"
|
|
19
|
-
lists = klass.instance_methods(false)
|
|
20
|
-
lists.each { |method| method_wrapper(klass, method, driver, webdriver_wait, pages) }
|
|
21
|
-
class_decorator(klass.superclass, driver, webdriver_wait, pages)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def self.ui_decorator(driver, webdriver_wait, pages)
|
|
25
|
-
klass = pages.get.last
|
|
26
|
-
class_decorator(klass, driver, webdriver_wait, pages)
|
|
27
|
-
klass.new
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
data/lib/ui_object.rb
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
module Citronella
|
|
2
|
-
module UiObject
|
|
3
|
-
class Ui
|
|
4
|
-
def initialize(driver, webdriver_wait, pages, locator, page, exception)
|
|
5
|
-
@driver = driver
|
|
6
|
-
@webdriver_wait = Selenium::WebDriver::Wait.new(timeout: webdriver_wait)
|
|
7
|
-
@pages = pages
|
|
8
|
-
@locator = locator
|
|
9
|
-
@page = page
|
|
10
|
-
@exception = exception
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def send_keys(text, enter: false)
|
|
14
|
-
@webdriver_wait.until { @driver.find_element(@locator).displayed? }
|
|
15
|
-
el = @driver.find_element(@locator)
|
|
16
|
-
el.send_keys text
|
|
17
|
-
|
|
18
|
-
if enter
|
|
19
|
-
el.send_keys :return
|
|
20
|
-
|
|
21
|
-
if @page
|
|
22
|
-
@pages.get << @page
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def click
|
|
28
|
-
@webdriver_wait.until { @driver.find_element(@locator).displayed? }
|
|
29
|
-
@driver.find_element(@locator).click
|
|
30
|
-
|
|
31
|
-
if @page
|
|
32
|
-
@pages.get << @page
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def get_elements
|
|
37
|
-
@webdriver_wait.until { @driver.find_element(@locator).displayed? }
|
|
38
|
-
@driver.find_elements(@locator)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|