cucu 1.0.0__py3-none-any.whl

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.

Potentially problematic release.


This version of cucu might be problematic. Click here for more details.

Files changed (83) hide show
  1. cucu/__init__.py +38 -0
  2. cucu/ansi_parser.py +58 -0
  3. cucu/behave_tweaks.py +196 -0
  4. cucu/browser/__init__.py +0 -0
  5. cucu/browser/core.py +80 -0
  6. cucu/browser/frames.py +106 -0
  7. cucu/browser/selenium.py +323 -0
  8. cucu/browser/selenium_tweaks.py +27 -0
  9. cucu/cli/__init__.py +3 -0
  10. cucu/cli/core.py +788 -0
  11. cucu/cli/run.py +207 -0
  12. cucu/cli/steps.py +137 -0
  13. cucu/cli/thread_dumper.py +55 -0
  14. cucu/config.py +440 -0
  15. cucu/edgedriver_autoinstaller/README.md +1 -0
  16. cucu/edgedriver_autoinstaller/__init__.py +37 -0
  17. cucu/edgedriver_autoinstaller/utils.py +231 -0
  18. cucu/environment.py +283 -0
  19. cucu/external/jquery/jquery-3.5.1.min.js +2 -0
  20. cucu/formatter/__init__.py +0 -0
  21. cucu/formatter/cucu.py +261 -0
  22. cucu/formatter/json.py +321 -0
  23. cucu/formatter/junit.py +289 -0
  24. cucu/fuzzy/__init__.py +3 -0
  25. cucu/fuzzy/core.py +107 -0
  26. cucu/fuzzy/fuzzy.js +253 -0
  27. cucu/helpers.py +875 -0
  28. cucu/hooks.py +205 -0
  29. cucu/language_server/__init__.py +3 -0
  30. cucu/language_server/core.py +114 -0
  31. cucu/lint/__init__.py +0 -0
  32. cucu/lint/linter.py +397 -0
  33. cucu/lint/rules/format.yaml +125 -0
  34. cucu/logger.py +113 -0
  35. cucu/matcher/__init__.py +0 -0
  36. cucu/matcher/core.py +30 -0
  37. cucu/page_checks.py +63 -0
  38. cucu/reporter/__init__.py +3 -0
  39. cucu/reporter/external/bootstrap.min.css +7 -0
  40. cucu/reporter/external/bootstrap.min.js +7 -0
  41. cucu/reporter/external/dataTables.bootstrap.min.css +1 -0
  42. cucu/reporter/external/dataTables.bootstrap.min.js +14 -0
  43. cucu/reporter/external/jquery-3.5.1.min.js +2 -0
  44. cucu/reporter/external/jquery.dataTables.min.js +192 -0
  45. cucu/reporter/external/popper.min.js +5 -0
  46. cucu/reporter/favicon.png +0 -0
  47. cucu/reporter/html.py +452 -0
  48. cucu/reporter/templates/feature.html +72 -0
  49. cucu/reporter/templates/flat.html +48 -0
  50. cucu/reporter/templates/index.html +49 -0
  51. cucu/reporter/templates/layout.html +109 -0
  52. cucu/reporter/templates/scenario.html +200 -0
  53. cucu/steps/__init__.py +27 -0
  54. cucu/steps/base_steps.py +88 -0
  55. cucu/steps/browser_steps.py +337 -0
  56. cucu/steps/button_steps.py +91 -0
  57. cucu/steps/checkbox_steps.py +111 -0
  58. cucu/steps/command_steps.py +181 -0
  59. cucu/steps/comment_steps.py +17 -0
  60. cucu/steps/draggable_steps.py +168 -0
  61. cucu/steps/dropdown_steps.py +467 -0
  62. cucu/steps/file_input_steps.py +80 -0
  63. cucu/steps/filesystem_steps.py +144 -0
  64. cucu/steps/flow_control_steps.py +198 -0
  65. cucu/steps/image_steps.py +37 -0
  66. cucu/steps/input_steps.py +301 -0
  67. cucu/steps/link_steps.py +63 -0
  68. cucu/steps/menuitem_steps.py +39 -0
  69. cucu/steps/platform_steps.py +29 -0
  70. cucu/steps/radio_steps.py +187 -0
  71. cucu/steps/step_utils.py +55 -0
  72. cucu/steps/tab_steps.py +68 -0
  73. cucu/steps/table_steps.py +437 -0
  74. cucu/steps/tables.js +28 -0
  75. cucu/steps/text_steps.py +78 -0
  76. cucu/steps/variable_steps.py +100 -0
  77. cucu/steps/webserver_steps.py +40 -0
  78. cucu/utils.py +269 -0
  79. cucu-1.0.0.dist-info/METADATA +424 -0
  80. cucu-1.0.0.dist-info/RECORD +83 -0
  81. cucu-1.0.0.dist-info/WHEEL +4 -0
  82. cucu-1.0.0.dist-info/entry_points.txt +2 -0
  83. cucu-1.0.0.dist-info/licenses/LICENSE +32 -0
@@ -0,0 +1,289 @@
1
+ # -*- coding: utf-8 -*-
2
+ import os
3
+ import traceback
4
+ from datetime import datetime
5
+ from xml.sax.saxutils import escape
6
+
7
+ import bs4
8
+ from behave.formatter.base import Formatter
9
+ from behave.model_core import Status
10
+ from bs4.formatter import XMLFormatter
11
+ from tenacity import RetryError
12
+
13
+ from cucu.config import CONFIG
14
+ from cucu.utils import ellipsize_filename
15
+
16
+
17
+ class CucuJUnitFormatter(Formatter):
18
+ name = "cucu-junit"
19
+ description = "JUnit Formater"
20
+
21
+ def __init__(self, stream_opener, config):
22
+ super(CucuJUnitFormatter, self).__init__(stream_opener, config)
23
+ self.xml_root = None
24
+ self.current_feature = None
25
+ self.current_scenario = None
26
+ self.curent_step = None
27
+ self.current_scenario_traceback = None
28
+ self.current_scenario_duration = 0.0
29
+ self.current_scenario_results = {}
30
+ self.feature_timestamp = None
31
+ self.steps = []
32
+
33
+ # -- FORMATTER API:
34
+ def uri(self, uri):
35
+ pass
36
+
37
+ def feature(self, feature):
38
+ self.current_feature = feature
39
+ date_now = datetime.now()
40
+ self.feature_results = {
41
+ "name": escape(feature.name),
42
+ "foldername": escape(ellipsize_filename(feature.name)),
43
+ "tests": 0,
44
+ "errors": 0,
45
+ "failures": 0,
46
+ "skipped": 0,
47
+ "timestamp": date_now.strftime("%Y-%m-%dT%H:%M:%S.%f%z"),
48
+ "scenarios": {},
49
+ }
50
+ if feature.tags:
51
+ self.feature_results["tags"] = ", ".join(feature.tags)
52
+
53
+ def background(self, background):
54
+ # -- ADD BACKGROUND STEPS: Support *.feature file regeneration.
55
+ for step_ in background.steps:
56
+ self.step(step_)
57
+
58
+ def update_scenario(self):
59
+ if self.current_scenario is not None:
60
+ self.current_scenario_results["time"] = str(
61
+ round(self.current_scenario_duration, 3)
62
+ )
63
+ hook_failed = self.current_scenario.hook_failed
64
+ if hook_failed:
65
+ status = "errored"
66
+ else:
67
+ status = self.current_scenario.compute_status().name
68
+
69
+ self.current_scenario_results["status"] = status
70
+ failures = []
71
+
72
+ if status == "failed" and self.current_scenario_traceback:
73
+ failure_handlers = CONFIG["__CUCU_CUSTOM_FAILURE_HANDLERS"]
74
+
75
+ for failure_handler in failure_handlers:
76
+ failures.append(
77
+ failure_handler(
78
+ self.current_feature, self.current_scenario
79
+ )
80
+ )
81
+
82
+ failures += [
83
+ f"{self.current_step.keyword} {self.current_step.name} (after {round(self.current_step.duration, 3)}s)"
84
+ ]
85
+
86
+ if error := self.current_step.exception:
87
+ if isinstance(error, RetryError):
88
+ error = error.last_attempt.exception()
89
+
90
+ if len(error.args) > 0 and isinstance(error.args[0], str):
91
+ error_class_name = error.__class__.__name__
92
+ error_lines = error.args[0].splitlines()
93
+ error_lines[0] = (
94
+ f"{error_class_name}: {error_lines[0]}"
95
+ )
96
+ else:
97
+ error_lines = [repr(error)]
98
+ failures += error_lines
99
+
100
+ if CONFIG["CUCU_JUNIT_WITH_STACKTRACE"] == "true":
101
+ failures += traceback.format_tb(
102
+ self.current_scenario_traceback
103
+ )
104
+
105
+ self.current_scenario_results["failure"] = failures
106
+
107
+ if status == "skipped":
108
+ self.current_scenario_results["skipped"] = True
109
+
110
+ def scenario(self, scenario):
111
+ self.update_scenario()
112
+
113
+ self.current_scenario = scenario
114
+ self.current_scenario_traceback = None
115
+ self.current_scenario_duration = 0.0
116
+ self.current_scenario_results = {
117
+ "status": "pending",
118
+ "time": "n/a",
119
+ "failure": None,
120
+ "skipped": None,
121
+ }
122
+ self.current_scenario_results["foldername"] = escape(
123
+ ellipsize_filename(scenario.name)
124
+ )
125
+ if scenario.tags:
126
+ self.current_scenario_results["tags"] = ", ".join(scenario.tags)
127
+
128
+ scenario_name = escape(scenario.name)
129
+ self.feature_results["scenarios"][scenario_name] = (
130
+ self.current_scenario_results
131
+ )
132
+
133
+ # we write out every new scenario into the JUnit results output
134
+ # which allows us to have a valid JUnit XML results file per feature
135
+ # file currently running even if the process crashes or is killed so we
136
+ # can still generate valid reports from every run.
137
+ self.write_results(self.feature_results)
138
+
139
+ def match(self, step):
140
+ pass
141
+
142
+ def step(self, step):
143
+ self.steps.append(step)
144
+
145
+ def insert_step(self, step, index=-1):
146
+ if index == -1:
147
+ self.steps.append(step)
148
+ else:
149
+ self.steps.insert(index, step)
150
+
151
+ def result(self, step):
152
+ self.current_step = step
153
+ if step.status == Status.failed:
154
+ self.current_scenario_traceback = step.exc_traceback
155
+
156
+ self.current_scenario_duration += step.duration
157
+
158
+ def eof(self):
159
+ """
160
+ end of file for the feature and this is when we write the updated
161
+ results to the file
162
+ """
163
+ self.update_scenario()
164
+ self.write_results(self.feature_results)
165
+
166
+ def close(self):
167
+ pass
168
+
169
+ def write_results(self, results):
170
+ """
171
+ given a feature results dictionary that looks like so:
172
+ {
173
+ "name": "name of the feature",
174
+ "foldername": "",
175
+ "tests": 0,
176
+ "errors": 0,
177
+ "failures": 0,
178
+ "skipped": 0,
179
+ "timestamp": "",
180
+ "scenarios": {
181
+ "scenario name": {
182
+ "foldername": "",
183
+ "tags": "DOM-3435, testrail(3366,45891)",
184
+ "status": "passed/failed/skipped",
185
+ "time": "0.0000":
186
+ "stdout": "",
187
+ "stderr": "",
188
+ },
189
+ ...
190
+ }
191
+ }
192
+ """
193
+
194
+ # custom attribute ordering so attributes are printed in a consistent
195
+ # and desired order
196
+ class SortAttributes(XMLFormatter):
197
+ def attributes(self, tag):
198
+ ordered = [
199
+ "classname",
200
+ "name",
201
+ "foldername",
202
+ "tests",
203
+ "errors",
204
+ "failures",
205
+ "skipped",
206
+ "status",
207
+ "timestamp",
208
+ "time",
209
+ "tags",
210
+ ]
211
+
212
+ return [
213
+ (attr, tag[attr]) for attr in ordered if attr in tag.attrs
214
+ ]
215
+
216
+ soup = bs4.BeautifulSoup()
217
+ testsuite = bs4.Tag(name="testsuite")
218
+ testsuite["name"] = results["name"]
219
+ testsuite["foldername"] = results["foldername"]
220
+ testsuite["timestamp"] = results["timestamp"]
221
+
222
+ junit_dir = CONFIG["CUCU_JUNIT_DIR"]
223
+ os.makedirs(junit_dir, exist_ok=True)
224
+
225
+ feature_name = results["name"]
226
+ output_filepath = os.path.join(junit_dir, f"{feature_name}.xml")
227
+
228
+ scenarios = results["scenarios"]
229
+
230
+ if CONFIG["CUCU_SHOW_SKIPS"] != "true":
231
+ filtered_scenarios = {}
232
+
233
+ for name, scenario in scenarios.items():
234
+ if scenario["status"] != "skipped":
235
+ filtered_scenarios[name] = scenario
236
+
237
+ scenarios = filtered_scenarios
238
+
239
+ if len(scenarios) == 0:
240
+ # we had a suite of just skipped results
241
+ return
242
+
243
+ # calculate with the latest data
244
+ testsuite["tests"] = len(scenarios)
245
+ testsuite["failures"] = len(
246
+ [x for x in scenarios.values() if x["status"] == Status.failed]
247
+ )
248
+ testsuite["skipped"] = len(
249
+ [x for x in scenarios.values() if x["status"] == Status.skipped]
250
+ )
251
+ testsuite["errors"] = len(
252
+ [
253
+ x
254
+ for x in scenarios.values()
255
+ if x["status"]
256
+ not in (Status.failed, Status.skipped, Status.passed)
257
+ ]
258
+ )
259
+
260
+ if "tags" in results:
261
+ testsuite["tags"] = results["tags"]
262
+ soup.append(testsuite)
263
+
264
+ for scenario_name in scenarios:
265
+ scenario = scenarios[scenario_name]
266
+ testcase = bs4.Tag(name="testcase")
267
+ testcase["classname"] = results["name"]
268
+ testcase["name"] = scenario_name
269
+ testcase["foldername"] = scenario["foldername"]
270
+ if "tags" in scenario:
271
+ testcase["tags"] = scenario["tags"]
272
+ testcase["status"] = scenario["status"]
273
+ testcase["time"] = scenario["time"]
274
+
275
+ if scenario["failure"] is not None:
276
+ failure_message = "\n".join(scenario["failure"])
277
+ failure = bs4.Tag(name="failure")
278
+ failure.append(bs4.CData(failure_message))
279
+ testcase.append(failure)
280
+
281
+ if scenario["skipped"] is not None:
282
+ testcase.append(bs4.Tag(name="skipped"))
283
+
284
+ testsuite.append(testcase)
285
+
286
+ feature_name = results["name"]
287
+ output_filepath = os.path.join(junit_dir, f"{feature_name}.xml")
288
+ with open(output_filepath, "w", encoding="utf-8") as output:
289
+ output.write(soup.prettify(formatter=SortAttributes()))
cucu/fuzzy/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ # flake8: noqa
2
+ # nopycln: file
3
+ from .core import init, find, load_jquery_lib, load_fuzzy_lib, Direction
cucu/fuzzy/core.py ADDED
@@ -0,0 +1,107 @@
1
+ import pkgutil
2
+ from enum import Enum
3
+
4
+ from cucu import logger
5
+ from cucu.browser.frames import search_in_all_frames
6
+
7
+
8
+ def load_jquery_lib():
9
+ """
10
+ load jquery library
11
+ """
12
+ jquery_lib = pkgutil.get_data(
13
+ "cucu", "external/jquery/jquery-3.5.1.min.js"
14
+ )
15
+ return jquery_lib.decode("utf8")
16
+
17
+
18
+ def load_fuzzy_lib():
19
+ """
20
+ load the fuzzy javascript library
21
+ """
22
+ return pkgutil.get_data("cucu", "fuzzy/fuzzy.js").decode("utf8")
23
+
24
+
25
+ def init(browser):
26
+ """
27
+ initializes the fuzzy matching javascript library within the currently open
28
+ browsers execution engine
29
+
30
+ parameters:
31
+ browser - ...
32
+ """
33
+ script = "return typeof cucu !== 'undefined' && typeof cucu.fuzzy_find === 'function';"
34
+ cucu_injected = browser.execute(script)
35
+ if cucu_injected:
36
+ # cucu fuzzy find already exists
37
+ return
38
+
39
+ logger.debug("inject cucu fuzzy find library to the browser")
40
+ jqCucu_script = "window.jqCucu = jQuery.noConflict(true);"
41
+ browser.execute(load_jquery_lib() + jqCucu_script + load_fuzzy_lib())
42
+
43
+
44
+ class Direction(Enum):
45
+ """
46
+ simple Direction enum
47
+ """
48
+
49
+ LEFT_TO_RIGHT = 1
50
+ RIGHT_TO_LEFT = 2
51
+
52
+
53
+ def find(
54
+ browser,
55
+ name,
56
+ things,
57
+ index=0,
58
+ direction=Direction.LEFT_TO_RIGHT,
59
+ name_within_thing=False,
60
+ ):
61
+ """
62
+ find an element by applying the fuzzy finding rules when given the name
63
+ that identifies the element on screen and a list of possible `things` that
64
+ are CSS expression fragments like so:
65
+
66
+ tag_name[attribute expression]
67
+
68
+ That identify the kind of element you're trying to find, such as a button,
69
+ input[type='button'], etc.
70
+
71
+ parameters:
72
+ browser - the cucu.browser.Browser object
73
+ name - name that identifies the element you are trying to find
74
+ things - array of CSS fragments that specify the kind of elements you
75
+ want to match on
76
+ index - which of the many matches to return
77
+ direction - the text to element direction to apply fuzzy in. Default we
78
+ apply right to left but for checkboxes or certain languages
79
+ this direction can be used to find things by prioritizing
80
+ matching from "left to right"
81
+ name_within_thing - to determine if the name has to be within the web element
82
+
83
+ returns:
84
+ the WebElement that matches the provided arguments.
85
+ """
86
+ browser.switch_to_default_frame()
87
+
88
+ # always need to protect names in which double quotes are used as below
89
+ # we pass arguments to the fuzzy_find javascript function wrapped in double
90
+ # quotes
91
+ name = name.replace('"', '\\"')
92
+ name_within_thing = "true" if name_within_thing else "false"
93
+
94
+ args = [
95
+ f'"{name}"',
96
+ str(things),
97
+ str(index),
98
+ str(direction.value),
99
+ name_within_thing,
100
+ ]
101
+
102
+ def execute_fuzzy_find():
103
+ init(browser)
104
+ script = f"return cucu.fuzzy_find({','.join(args)});"
105
+ return browser.execute(script)
106
+
107
+ return search_in_all_frames(browser, execute_fuzzy_find)
cucu/fuzzy/fuzzy.js ADDED
@@ -0,0 +1,253 @@
1
+ (function(){
2
+ window.cucu = {
3
+ debug: false
4
+ };
5
+
6
+ /*
7
+ * custom jquery matchers:
8
+ *
9
+ *
10
+ * has_text - matches an element with the exact text provided.
11
+ *
12
+ * vis - matches an element that is visible and has at least
13
+ * one visible parent.
14
+ *
15
+ */
16
+ jqCucu.extend(
17
+ jqCucu.expr[ ":" ],
18
+ {
19
+ has_text: function(elem, index, match) {
20
+ return (elem.textContent || elem.innerText || jqCucu(elem).text() || '').trim() === match[3].trim();
21
+ },
22
+ vis: function (elem) {
23
+ return !(jqCucu(elem).is(":hidden") || jqCucu(elem).css("width") == "0px" || jqCucu(elem).css("height") == "0px" || jqCucu(elem).parents(":hidden").length);
24
+ }
25
+ }
26
+ );
27
+
28
+ // this matches the class Direction in cucu/fuzzy/core.py
29
+ const LEFT_TO_RIGHT = 1;
30
+ const RIGHT_TO_LEFT = 2;
31
+
32
+ /*
33
+ * find an element by applying the fuzzy finding rules when given the name
34
+ * that identifies the element on screen and a list of possible `things` that
35
+ * are CSS expression fragments like so:
36
+ *
37
+ * tag_name[attribute expression]
38
+ *
39
+ * That identify the kind of element you're trying to find, such as a button,
40
+ * input[type='button'], etc.
41
+ *
42
+ * parameters:
43
+ * name - name that identifies the element you are trying to find
44
+ * things - array of CSS fragments that specify the kind of elements
45
+ * you want to match on
46
+ * index - which of the many matches to return
47
+ * direction - the text to element direction to apply fuzzy in. Default
48
+ * we apply right to left but for checkboxes or certain
49
+ * languages this direction can be used to find things by
50
+ * prioritizing matching from "left to right"
51
+ * name_within_thing - to determine if the name has to be within the web element
52
+ */
53
+ cucu.fuzzy_find = function(name,
54
+ things,
55
+ index=0,
56
+ direction=LEFT_TO_RIGHT,
57
+ name_within_thing=false) {
58
+ var elements = [];
59
+ var results = null;
60
+ var attributes = ['aria-label', 'title', 'placeholder', 'value'];
61
+ var matchers = ['has_text', 'contains'];
62
+
63
+ name = name.replaceAll('"', '\\"')
64
+
65
+ /*
66
+ * try to match on exact text but ultimately fall back to matching on
67
+ * the elements text that contains the string we're searching for
68
+ */
69
+ for(var mIndex=0; mIndex < matchers.length; mIndex++) {
70
+ var matcher = matchers[mIndex];
71
+
72
+ /*
73
+ * element labeled in itself or by its own attributes
74
+ */
75
+ for(var tIndex = 0; tIndex < things.length; tIndex++) {
76
+ var thing = things[tIndex];
77
+
78
+ /*
79
+ * <thing>name</thing>
80
+ */
81
+ results = jqCucu(thing + ':vis:' + matcher + '("' + name + '")', document.body).toArray();
82
+ if (cucu.debug) { console.log('<thing>name</thing>', results); }
83
+ elements = elements.concat(results);
84
+
85
+ // <thing attribute="name"></thing>
86
+ for(var aIndex=0; aIndex < attributes.length; aIndex++) {
87
+ var attribute_name = attributes[aIndex];
88
+ if (matcher == 'has_text') {
89
+ results = jqCucu(thing + '[' + attribute_name + '="' + name + '"]:vis', document.body).toArray();
90
+ if (cucu.debug) { console.log('<thing attribute="name"></thing>', results); }
91
+ } else if (matcher == 'contains') {
92
+ results = jqCucu(thing + '[' + attribute_name + '*="' + name + '"]:vis', document.body).toArray();
93
+ if (cucu.debug) { console.log('<thing attribute*="name"></thing>', results); }
94
+ }
95
+ elements = elements.concat(results);
96
+ }
97
+ }
98
+
99
+ /*
100
+ * validate against the `value` attribute of the actual DOM object
101
+ * as that has the value that may have changed from having written
102
+ * a new value into an input which doesn't get reflected in the DOM.
103
+ *
104
+ * TODO: I think there may be a cleaner way to handle this but for
105
+ * now lets just add another loop in here.
106
+ */
107
+ for(var tIndex = 0; tIndex < things.length; tIndex++) {
108
+ var thing = things[tIndex];
109
+
110
+ // <thing value="name"></thing>
111
+ if (matcher == 'has_text') {
112
+ results = jqCucu(thing + ':vis', document.body).filter(function(){
113
+ return this.value == name;
114
+ }).toArray();
115
+ if (cucu.debug) { console.log('<thing value="name"></thing>', results); }
116
+ } else if (matcher == 'contains') {
117
+ results = jqCucu(thing + ':vis', document.body).filter(function(){
118
+ return this.value !== undefined && String(this.value).indexOf(name) != -1;
119
+ }).toArray();
120
+ if (cucu.debug) { console.log('<thing value*="name"></thing>', results); }
121
+ }
122
+ elements = elements.concat(results);
123
+ }
124
+
125
+ /*
126
+ * element labeled by another using the for/id attributes
127
+ */
128
+ var labels = jqCucu('*[for]:vis:' + matcher + '("' + name + '")', document.body).toArray();
129
+ for(var tIndex = 0; tIndex < things.length; tIndex++) {
130
+ var thing = things[tIndex];
131
+ results = [];
132
+
133
+ /*
134
+ * <* for=...>name</*>...<thing id=...></thing>
135
+ */
136
+ for(var lIndex=0; lIndex < labels.length; lIndex++) {
137
+ var label = labels[lIndex];
138
+ var id = label.getAttribute('for');
139
+ results = jqCucu(thing + '[id="' + id + '"]:vis', document.body).toArray();
140
+ if (cucu.debug) { console.log('<* for=...>name</*>...<thing id=...></thing>', results); }
141
+ elements = elements.concat(results);
142
+ }
143
+ }
144
+
145
+ /*
146
+ * element labeled by a nested child
147
+ */
148
+ for(var tIndex = 0; tIndex < things.length; tIndex++) {
149
+ var thing = things[tIndex];
150
+
151
+ /*
152
+ * <thing><*>...name...</*></thing>
153
+ */
154
+ results = jqCucu('*:vis:' + matcher + '("' + name + '")', document.body).parents(thing).toArray();
155
+ if (cucu.debug) { console.log('<thing><*>...name...</*></thing>', results); }
156
+ elements = elements.concat(results);
157
+
158
+ // <thing><* attribute="name"></*></thing>
159
+ for(var aIndex=0; aIndex < attributes.length; aIndex++) {
160
+ var attribute_name = attributes[aIndex];
161
+ results = jqCucu('*:vis[' + attribute_name + '="' + name + '"]', document.body).parents(thing).toArray();
162
+ if (cucu.debug) { console.log('<thing><* attibute="name"></*></thing>', results); }
163
+ elements = elements.concat(results);
164
+ }
165
+ }
166
+
167
+ // if the name has to be within the element, the following rules are not considered
168
+ if (name_within_thing) {
169
+ continue;
170
+ }
171
+
172
+ // element labeled with direct previous sibling
173
+ if (direction === LEFT_TO_RIGHT) {
174
+ for(var tIndex = 0; tIndex < things.length; tIndex++) {
175
+ var thing = things[tIndex];
176
+
177
+ // <*>name</*><thing/>
178
+ results = jqCucu('*:vis:' + matcher + '("' + name + '")', document.body).next(thing + ':vis').toArray();
179
+ if (cucu.debug) { console.log('<*>name</*><thing/>', results); }
180
+ elements = elements.concat(results);
181
+ }
182
+ }
183
+
184
+ // element labeled with direct next sibling
185
+ if (direction === RIGHT_TO_LEFT) {
186
+ for(var tIndex = 0; tIndex < things.length; tIndex++) {
187
+ var thing = things[tIndex];
188
+
189
+ // <thing/><*>name</*>
190
+ results = jqCucu('*:vis:' + matcher + '("' + name + '")', document.body).prev(thing).toArray();
191
+ if (cucu.debug) { console.log('<thing/><*>name</*>', results); }
192
+ elements = elements.concat(results);
193
+ }
194
+ }
195
+
196
+ /*
197
+ * element labeled by a text sibling
198
+ */
199
+ for(var tIndex = 0; tIndex < things.length; tIndex++) {
200
+ var thing = things[tIndex];
201
+
202
+ /*
203
+ * <*><thing></thing>name</*>
204
+ */
205
+ results = jqCucu('*:vis:' + matcher + '("' + name + '")', document.body).children(thing + ':vis').toArray();
206
+ if (cucu.debug) { console.log('<*><thing></thing>name</*>', results); }
207
+ elements = elements.concat(results);
208
+ }
209
+
210
+ // element labeled with any previous sibling
211
+ if (direction === LEFT_TO_RIGHT) {
212
+ for(var tIndex = 0; tIndex < things.length; tIndex++) {
213
+ var thing = things[tIndex];
214
+
215
+ // <*>name</*>...<thing>...
216
+ results = jqCucu('*:vis:' + matcher + '("' + name + '")', document.body).nextAll(thing + ':vis').toArray();
217
+ if (cucu.debug) { console.log('<*>name</*>...<thing>...', results); }
218
+ elements = elements.concat(results);
219
+
220
+ // <...><*>name</*></...>...<...><thing></...>
221
+ // XXX: this rule is horribly complicated and I'd rather see it gone
222
+ // basically: common great grandpranet
223
+ results = jqCucu('*:vis:' + matcher + '("' + name + '")', document.body).nextAll().find(thing + ':vis').toArray();
224
+ if (cucu.debug) { console.log('<...><*>name</*></...>...<...><thing></...>', results); }
225
+ elements = elements.concat(results);
226
+ }
227
+ }
228
+
229
+ // element labeled with any next sibling
230
+ if (direction === RIGHT_TO_LEFT) {
231
+ for(var tIndex = 0; tIndex < things.length; tIndex++) {
232
+ var thing = things[tIndex];
233
+
234
+ // next siblings: <thing>...<*>name</*>...
235
+ results = jqCucu('*:vis:' + matcher + '("' + name + '")', document.body).prevAll(thing).toArray();
236
+ if (cucu.debug) { console.log('<thing>...<*>name</*>...', results); }
237
+ elements = elements.concat(results);
238
+
239
+ // <...><thing></...>...<...><*>name</*></...>
240
+ // XXX: this rule is horribly complicated and I'd rather see it gone
241
+ results = jqCucu('*:vis:' + matcher + '("' + name + '")', document.body).prevAll().find(thing + ':vis').toArray();
242
+ if (cucu.debug) { console.log('<...><thin></...>...<...><*>name</*></...>', results); }
243
+ elements = elements.concat(results);
244
+ }
245
+ }
246
+ }
247
+
248
+ if (cucu.debug) {
249
+ console.log(elements);
250
+ }
251
+ return elements[index];
252
+ };
253
+ })();