howdoc 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +25 -0
- data/lib/howdoc/capybara_actions.rb +48 -13
- data/lib/howdoc/configuration.rb +45 -5
- data/lib/howdoc/marker.js +113 -0
- data/lib/howdoc/marker.rb +59 -0
- data/lib/howdoc/recorder.rb +2 -2
- data/lib/howdoc/screenshot.rb +8 -2
- data/lib/howdoc/version.rb +1 -1
- data/lib/howdoc.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9201c07602657e340541387ddb7704ca43fcde6183ce52fd1273f036dd87513b
|
|
4
|
+
data.tar.gz: 2f9bc01470635e9d626141909f12196cb950e41e80c11077aa78943216291259
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c970647ba28984988f284e173b3a675123cee78b35a66a1c0ff8c5d7ed4d6ca856e41dbf3b7e8eade8a4b852121a636d86869d1f002007f5df3eba6b5c9ab10a
|
|
7
|
+
data.tar.gz: ea877687bd2a0ef1c550769f96d5cfea425ccda4f74ee1d4d71dde7575f5f473c13e97a13998d71f9958d01aa6fbeed69ca3daeb056ad0448e32f5f2ab42d3a9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
Screenshots can now show what the step is about, the way a guide made by hand
|
|
6
|
+
does: an arrow on the button being pressed, a highlight around the words being
|
|
7
|
+
looked for.
|
|
8
|
+
|
|
9
|
+
- The marker is drawn in the browser between sizing the window and taking the
|
|
10
|
+
picture, and removed again straight after -- including when the picture could
|
|
11
|
+
not be taken, so nothing is left pointing at what has since moved.
|
|
12
|
+
- An arrow's tip goes on the middle of a button, and below anything small
|
|
13
|
+
enough that the arrow would cover it.
|
|
14
|
+
- A highlight measures the words themselves, through a range around the match,
|
|
15
|
+
rather than the paragraph that happens to hold them.
|
|
16
|
+
- `config.illustrate_actions` illustrates every action rather than only the
|
|
17
|
+
moments a reader arrives somewhere. That is what makes the pointer worth
|
|
18
|
+
having: a picture per click, each one marked.
|
|
19
|
+
- `config.marker`, `config.marker_colour`, `config.marker_dim` and
|
|
20
|
+
`config.marker_size` decide whether and how it is drawn.
|
|
21
|
+
|
|
3
22
|
## 0.3.1
|
|
4
23
|
|
|
5
24
|
- The menu pass left a blank line behind every time it ran. A suite that builds
|
data/README.md
CHANGED
|
@@ -114,6 +114,31 @@ By default `visit` and `assert_text` are illustrated and the action verbs are
|
|
|
114
114
|
not, on the grounds that a picture is worth showing when the reader arrives
|
|
115
115
|
somewhere, not every time they press a button.
|
|
116
116
|
|
|
117
|
+
## Showing where to look
|
|
118
|
+
|
|
119
|
+
A screenshot of a busy page does not say which of forty things on it the step is
|
|
120
|
+
about, so howdoc draws the mark a person would draw by hand:
|
|
121
|
+
|
|
122
|
+
* an arrow with its tip on the button about to be pressed, on the field about to
|
|
123
|
+
be filled, on the menu about to be chosen from
|
|
124
|
+
* a highlight around the words an assertion was looking for
|
|
125
|
+
|
|
126
|
+
It is drawn in the browser between sizing the window and taking the picture, and
|
|
127
|
+
taken off again straight after, so the page the test goes on driving is the page
|
|
128
|
+
it was. There is no cursor in a headless screenshot to photograph -- the arrow is
|
|
129
|
+
drawn, and its tip moves below anything too small for it to sit on without
|
|
130
|
+
covering.
|
|
131
|
+
|
|
132
|
+
By default only the moments a reader arrives somewhere are illustrated. A guide
|
|
133
|
+
that shows every click is one line of configuration:
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
config.illustrate_actions = true # a picture per action, each one marked
|
|
137
|
+
config.marker_colour = '#e11d48' # the arrow and the highlight
|
|
138
|
+
config.marker_dim = true # dim the rest of the page around the mark
|
|
139
|
+
config.marker = false # or draw nothing at all
|
|
140
|
+
```
|
|
141
|
+
|
|
117
142
|
## Your own helpers
|
|
118
143
|
|
|
119
144
|
Real suites have helpers Capybara has no verb for -- a custom editor, a menu
|
|
@@ -30,7 +30,10 @@ module Howdoc
|
|
|
30
30
|
def fill_in(locator = nil, with:, currently_with: nil, fill_options: {}, **options)
|
|
31
31
|
doc = Howdoc.extract_options!(options)
|
|
32
32
|
|
|
33
|
-
narrate(
|
|
33
|
+
narrate(
|
|
34
|
+
fill_in_action(locator), doc,
|
|
35
|
+
target: [:fillable_field, locator], field: howdoc_field_label(locator), value: with
|
|
36
|
+
)
|
|
34
37
|
|
|
35
38
|
super(locator, with:, currently_with:, fill_options:, **options)
|
|
36
39
|
end
|
|
@@ -38,7 +41,7 @@ module Howdoc
|
|
|
38
41
|
def select(value = nil, from: nil, **options)
|
|
39
42
|
doc = Howdoc.extract_options!(options)
|
|
40
43
|
|
|
41
|
-
narrate(:select, doc, value:, field: howdoc_field_label(from))
|
|
44
|
+
narrate(:select, doc, target: [:select, from], value:, field: howdoc_field_label(from))
|
|
42
45
|
|
|
43
46
|
super(value, from:, **options)
|
|
44
47
|
end
|
|
@@ -46,7 +49,7 @@ module Howdoc
|
|
|
46
49
|
def check(locator = nil, **options)
|
|
47
50
|
doc = Howdoc.extract_options!(options)
|
|
48
51
|
|
|
49
|
-
narrate(:check, doc, field: howdoc_field_label(locator))
|
|
52
|
+
narrate(:check, doc, target: [:checkbox, locator], field: howdoc_field_label(locator))
|
|
50
53
|
|
|
51
54
|
super(locator, **options)
|
|
52
55
|
end
|
|
@@ -54,7 +57,7 @@ module Howdoc
|
|
|
54
57
|
def uncheck(locator = nil, **options)
|
|
55
58
|
doc = Howdoc.extract_options!(options)
|
|
56
59
|
|
|
57
|
-
narrate(:uncheck, doc, field: howdoc_field_label(locator))
|
|
60
|
+
narrate(:uncheck, doc, target: [:checkbox, locator], field: howdoc_field_label(locator))
|
|
58
61
|
|
|
59
62
|
super(locator, **options)
|
|
60
63
|
end
|
|
@@ -62,7 +65,7 @@ module Howdoc
|
|
|
62
65
|
def choose(locator = nil, **options)
|
|
63
66
|
doc = Howdoc.extract_options!(options)
|
|
64
67
|
|
|
65
|
-
narrate(:choose, doc, field: howdoc_field_label(locator))
|
|
68
|
+
narrate(:choose, doc, target: [:radio_button, locator], field: howdoc_field_label(locator))
|
|
66
69
|
|
|
67
70
|
super(locator, **options)
|
|
68
71
|
end
|
|
@@ -70,7 +73,7 @@ module Howdoc
|
|
|
70
73
|
def click_button(locator = nil, **options)
|
|
71
74
|
doc = Howdoc.extract_options!(options)
|
|
72
75
|
|
|
73
|
-
narrate(:click_button, doc, locator:)
|
|
76
|
+
narrate(:click_button, doc, target: [:button, locator], locator:)
|
|
74
77
|
|
|
75
78
|
super(locator, **options)
|
|
76
79
|
end
|
|
@@ -78,7 +81,7 @@ module Howdoc
|
|
|
78
81
|
def click_link(locator = nil, **options)
|
|
79
82
|
doc = Howdoc.extract_options!(options)
|
|
80
83
|
|
|
81
|
-
narrate(:click_link, doc, locator:)
|
|
84
|
+
narrate(:click_link, doc, target: [:link, locator], locator:)
|
|
82
85
|
|
|
83
86
|
super(locator, **options)
|
|
84
87
|
end
|
|
@@ -86,7 +89,7 @@ module Howdoc
|
|
|
86
89
|
def click_on(locator = nil, **options)
|
|
87
90
|
doc = Howdoc.extract_options!(options)
|
|
88
91
|
|
|
89
|
-
narrate(:click_on, doc, locator:)
|
|
92
|
+
narrate(:click_on, doc, target: [:link_or_button, locator], locator:)
|
|
90
93
|
|
|
91
94
|
super(locator, **options)
|
|
92
95
|
end
|
|
@@ -95,7 +98,7 @@ module Howdoc
|
|
|
95
98
|
def attach_file(locator = nil, paths, make_visible: nil, **options)
|
|
96
99
|
doc = Howdoc.extract_options!(options)
|
|
97
100
|
|
|
98
|
-
narrate(:attach_file, doc, field: howdoc_field_label(locator))
|
|
101
|
+
narrate(:attach_file, doc, target: [:file_field, locator], field: howdoc_field_label(locator))
|
|
99
102
|
|
|
100
103
|
super(locator, paths, make_visible:, **options)
|
|
101
104
|
end
|
|
@@ -107,7 +110,10 @@ module Howdoc
|
|
|
107
110
|
doc = Howdoc.extract_options!(options)
|
|
108
111
|
result = super(locator, **options)
|
|
109
112
|
|
|
110
|
-
narrate(
|
|
113
|
+
narrate(
|
|
114
|
+
:assert_text, doc,
|
|
115
|
+
capture: true, arrival: true, text: locator, target: locator.to_s, marker_mode: :highlight
|
|
116
|
+
)
|
|
111
117
|
result
|
|
112
118
|
end
|
|
113
119
|
|
|
@@ -138,19 +144,48 @@ module Howdoc
|
|
|
138
144
|
host.nil? ? visit_uri.to_s : "https://#{host}#{visit_uri}"
|
|
139
145
|
end
|
|
140
146
|
|
|
141
|
-
def narrate(action, doc_options, capture: false, arrival: false,
|
|
147
|
+
def narrate(action, doc_options, capture: false, arrival: false, target: nil,
|
|
148
|
+
marker_mode: :pointer, **payload)
|
|
142
149
|
step = Howdoc.record(action, arrival:, nodoc: doc_options[:nodoc], **payload)
|
|
143
150
|
return nil if step.nil?
|
|
144
151
|
|
|
145
|
-
illustrate = doc_options.fetch(:screenshot, capture)
|
|
152
|
+
illustrate = doc_options.fetch(:screenshot, capture || illustrated_action?(target))
|
|
146
153
|
return step unless illustrate
|
|
147
154
|
|
|
148
155
|
Howdoc.capture(
|
|
149
156
|
page,
|
|
150
157
|
full_page: doc_options[:full_page] || false,
|
|
151
|
-
no_screenshot: doc_options[:no_screenshot] || false
|
|
158
|
+
no_screenshot: doc_options[:no_screenshot] || false,
|
|
159
|
+
marker: howdoc_marker(target), marker_mode:
|
|
152
160
|
)
|
|
153
161
|
step
|
|
154
162
|
end
|
|
163
|
+
|
|
164
|
+
# An action is illustrated only when the application asked for every click
|
|
165
|
+
# to be shown; a picture after each button is a lot of pictures, and worth
|
|
166
|
+
# it only when they are marked.
|
|
167
|
+
def illustrated_action?(target)
|
|
168
|
+
!target.nil? && Howdoc.config.illustrate_actions?
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# What the marker should point at. A string is the words an assertion was
|
|
172
|
+
# looking for, which the browser finds for itself; anything else names a
|
|
173
|
+
# Capybara selector and is resolved here, in whatever scope the test is in,
|
|
174
|
+
# exactly as the action about to happen would resolve it.
|
|
175
|
+
#
|
|
176
|
+
# Never waits: the action itself is entitled to wait, but a picture is not
|
|
177
|
+
# worth holding a suite up for, and a marker nobody could place is simply
|
|
178
|
+
# not drawn.
|
|
179
|
+
def howdoc_marker(target)
|
|
180
|
+
return nil if target.nil? || !Howdoc.config.marker?
|
|
181
|
+
return target if target.is_a?(String)
|
|
182
|
+
|
|
183
|
+
kind, locator = target
|
|
184
|
+
return nil if locator.nil?
|
|
185
|
+
|
|
186
|
+
page.find(kind, locator, match: :first, wait: 0)
|
|
187
|
+
rescue StandardError
|
|
188
|
+
nil
|
|
189
|
+
end
|
|
155
190
|
end
|
|
156
191
|
end
|
data/lib/howdoc/configuration.rb
CHANGED
|
@@ -35,6 +35,29 @@ module Howdoc
|
|
|
35
35
|
# page, so that guides do not mix wildly different image proportions.
|
|
36
36
|
attr_accessor :screenshot_height
|
|
37
37
|
|
|
38
|
+
# Whether a screenshot points at what the step is about: an arrow on the
|
|
39
|
+
# button being pressed, a highlight around the words being looked for. Off
|
|
40
|
+
# leaves the pictures exactly as the browser rendered them.
|
|
41
|
+
attr_accessor :marker
|
|
42
|
+
|
|
43
|
+
# The colour of that arrow and highlight. Red by convention, because a guide
|
|
44
|
+
# is read at a glance and red is the one colour an interface rarely uses for
|
|
45
|
+
# anything else.
|
|
46
|
+
attr_accessor :marker_colour
|
|
47
|
+
|
|
48
|
+
# Dims the rest of the page around what is marked. Emphatic, and worth it
|
|
49
|
+
# for a crowded screen; off by default because it changes every picture.
|
|
50
|
+
attr_accessor :marker_dim
|
|
51
|
+
|
|
52
|
+
# How tall the drawn pointer is, in CSS pixels.
|
|
53
|
+
attr_accessor :marker_size
|
|
54
|
+
|
|
55
|
+
# Whether an action -- a click, a choice, something typed -- is illustrated
|
|
56
|
+
# as well as narrated. Off by default: a picture belongs where the reader
|
|
57
|
+
# arrives somewhere new, not after every button. Turning it on is what makes
|
|
58
|
+
# the pointer worth having, so a guide shows every click.
|
|
59
|
+
attr_accessor :illustrate_actions
|
|
60
|
+
|
|
38
61
|
# Files in the output directory that the clean task must leave alone,
|
|
39
62
|
# typically hand-maintained landing pages.
|
|
40
63
|
attr_accessor :preserved_files
|
|
@@ -53,19 +76,28 @@ module Howdoc
|
|
|
53
76
|
attr_reader :locale_paths
|
|
54
77
|
|
|
55
78
|
def initialize
|
|
79
|
+
reset_defaults
|
|
80
|
+
@template_paths = []
|
|
81
|
+
@locale_paths = []
|
|
82
|
+
@field_label = nil
|
|
83
|
+
@sort_key = nil
|
|
84
|
+
@group_label = nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def reset_defaults
|
|
56
88
|
@root = 'doc/howdoc'
|
|
57
89
|
@enabled = false
|
|
58
90
|
@host = nil
|
|
59
91
|
@formats = %i[html]
|
|
60
92
|
@locales = nil
|
|
61
93
|
@screenshot_height = 940
|
|
94
|
+
@marker = true
|
|
95
|
+
@marker_colour = '#e11d48'
|
|
96
|
+
@marker_dim = false
|
|
97
|
+
@marker_size = 52
|
|
98
|
+
@illustrate_actions = false
|
|
62
99
|
@preserved_files = %w[index.html .keep]
|
|
63
100
|
@install_assets = true
|
|
64
|
-
@template_paths = []
|
|
65
|
-
@locale_paths = []
|
|
66
|
-
@field_label = nil
|
|
67
|
-
@sort_key = nil
|
|
68
|
-
@group_label = nil
|
|
69
101
|
end
|
|
70
102
|
|
|
71
103
|
def register_template_path(path)
|
|
@@ -105,5 +137,13 @@ module Howdoc
|
|
|
105
137
|
def enabled?
|
|
106
138
|
!!@enabled
|
|
107
139
|
end
|
|
140
|
+
|
|
141
|
+
def marker?
|
|
142
|
+
!!@marker
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def illustrate_actions?
|
|
146
|
+
!!@illustrate_actions
|
|
147
|
+
end
|
|
108
148
|
end
|
|
109
149
|
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Drawn over the page for as long as it takes to photograph it: an arrow on
|
|
2
|
+
// what is about to be clicked, a highlight around the words that were looked
|
|
3
|
+
// for. Called with no target, it takes off whatever it drew last time.
|
|
4
|
+
var MARKER = 'howdoc-marker';
|
|
5
|
+
var POINTER =
|
|
6
|
+
// The arrow, in a 24x32 box with its tip at 0,0 -- the shape every operating
|
|
7
|
+
// system has drawn since 1981, so a reader knows it without being told.
|
|
8
|
+
'M0 0 L0 22.6 L5.6 17.4 L9.4 25.9 L13.6 23.9 L9.8 15.7 L17.6 15.4 Z';
|
|
9
|
+
|
|
10
|
+
var target = arguments[0];
|
|
11
|
+
var options = arguments[1] || {};
|
|
12
|
+
|
|
13
|
+
var previous = document.getElementById(MARKER);
|
|
14
|
+
if (previous) { previous.parentNode.removeChild(previous); }
|
|
15
|
+
if (!target) { return null; }
|
|
16
|
+
|
|
17
|
+
var element = target;
|
|
18
|
+
var rect = null;
|
|
19
|
+
|
|
20
|
+
if (typeof target === 'string') {
|
|
21
|
+
// The words themselves, not the paragraph holding them: a range around
|
|
22
|
+
// the match measures what a reader would underline with a finger, where
|
|
23
|
+
// the element around it is as wide as the page.
|
|
24
|
+
var wanted = target.replace(/\\s+/g, ' ').trim();
|
|
25
|
+
var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null);
|
|
26
|
+
var node = null;
|
|
27
|
+
element = null;
|
|
28
|
+
|
|
29
|
+
while ((node = walker.nextNode())) {
|
|
30
|
+
var at = node.data.indexOf(wanted);
|
|
31
|
+
if (at === -1 || !node.parentElement || !node.parentElement.getClientRects().length) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
var range = document.createRange();
|
|
36
|
+
range.setStart(node, at);
|
|
37
|
+
range.setEnd(node, at + wanted.length);
|
|
38
|
+
rect = range.getBoundingClientRect();
|
|
39
|
+
element = node.parentElement;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!element || !element.getClientRects().length) { return null; }
|
|
45
|
+
if (!rect) { rect = element.getBoundingClientRect(); }
|
|
46
|
+
|
|
47
|
+
// A field dressed by a widget -- select2 and its like -- leaves the real
|
|
48
|
+
// <select> hidden and all but sizeless in a corner of what the reader actually
|
|
49
|
+
// sees. Point at the thing on the screen: the nearest ancestor that is really
|
|
50
|
+
// laid out.
|
|
51
|
+
if (rect.width < 8 || rect.height < 8) {
|
|
52
|
+
var holder = element.parentElement;
|
|
53
|
+
|
|
54
|
+
while (holder && holder !== document.body) {
|
|
55
|
+
var box = holder.getBoundingClientRect();
|
|
56
|
+
|
|
57
|
+
if (box.width >= 8 && box.height >= 8) { element = holder; rect = box; break; }
|
|
58
|
+
|
|
59
|
+
holder = holder.parentElement;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
var left = rect.left + window.scrollX;
|
|
63
|
+
var top = rect.top + window.scrollY;
|
|
64
|
+
var colour = options.colour || '#e11d48';
|
|
65
|
+
var padding = 6;
|
|
66
|
+
|
|
67
|
+
var layer = document.createElement('div');
|
|
68
|
+
layer.id = MARKER;
|
|
69
|
+
layer.style.cssText = 'position:absolute;left:0;top:0;width:0;height:0;' +
|
|
70
|
+
'z-index:2147483647;pointer-events:none;';
|
|
71
|
+
|
|
72
|
+
var ring = document.createElement('div');
|
|
73
|
+
ring.style.cssText = 'position:absolute;box-sizing:border-box;' +
|
|
74
|
+
'left:' + (left - padding) + 'px;top:' + (top - padding) + 'px;' +
|
|
75
|
+
'width:' + (rect.width + padding * 2) + 'px;' +
|
|
76
|
+
'height:' + (rect.height + padding * 2) + 'px;' +
|
|
77
|
+
'border:3px solid ' + colour + ';border-radius:8px;' +
|
|
78
|
+
(options.mode === 'highlight' ? 'background:' + colour + '1f;' : '') +
|
|
79
|
+
(options.dim ? 'box-shadow:0 0 0 9999px rgba(15,23,42,0.45);' : '');
|
|
80
|
+
layer.appendChild(ring);
|
|
81
|
+
|
|
82
|
+
if (options.mode !== 'highlight') {
|
|
83
|
+
var size = options.size || 52;
|
|
84
|
+
|
|
85
|
+
// The tip goes on the middle of a button, where a hand would put it, and a
|
|
86
|
+
// little below the middle on a field or a select: there the value is read
|
|
87
|
+
// along the top of the box, and an arrow through the middle of it sits in
|
|
88
|
+
// the way of the very thing the reader is meant to see.
|
|
89
|
+
//
|
|
90
|
+
// On something small -- a link in a table, a word in a row -- the arrow would
|
|
91
|
+
// cover what it points at whatever we do, so it hangs below instead.
|
|
92
|
+
var small = rect.width < 120 || rect.height < 32;
|
|
93
|
+
var tipLeft = left + rect.width / 2;
|
|
94
|
+
var tipTop = small ? top + rect.height - 2 : top + rect.height * 0.62;
|
|
95
|
+
var arrow = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
96
|
+
arrow.setAttribute('viewBox', '0 0 24 32');
|
|
97
|
+
arrow.setAttribute('width', size);
|
|
98
|
+
arrow.setAttribute('height', size * 32 / 24);
|
|
99
|
+
arrow.style.cssText = 'position:absolute;left:' + tipLeft + 'px;top:' + tipTop + 'px;' +
|
|
100
|
+
'filter:drop-shadow(0 2px 3px rgba(15,23,42,0.45));';
|
|
101
|
+
|
|
102
|
+
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
103
|
+
path.setAttribute('d', POINTER);
|
|
104
|
+
path.setAttribute('fill', '#ffffff');
|
|
105
|
+
path.setAttribute('stroke', colour);
|
|
106
|
+
path.setAttribute('stroke-width', '2.2');
|
|
107
|
+
path.setAttribute('stroke-linejoin', 'round');
|
|
108
|
+
arrow.appendChild(path);
|
|
109
|
+
layer.appendChild(arrow);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
document.body.appendChild(layer);
|
|
113
|
+
return true;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Howdoc
|
|
4
|
+
# Shows the reader where to look.
|
|
5
|
+
#
|
|
6
|
+
# A screenshot of a busy page does not say which of forty things on it the
|
|
7
|
+
# step is about. A guide made by hand solves that with a red circle drawn over
|
|
8
|
+
# the button; this draws the same thing, in the browser, in the moment between
|
|
9
|
+
# the window being sized and the picture being taken -- and takes it off again
|
|
10
|
+
# afterwards, so the page the test goes on driving is the page it was.
|
|
11
|
+
#
|
|
12
|
+
# There is no cursor in a headless screenshot to photograph, so the pointer is
|
|
13
|
+
# drawn: an arrow with its tip on whatever is about to be clicked. An assertion
|
|
14
|
+
# gets no arrow, because nobody is pointing at anything -- the words the reader
|
|
15
|
+
# should see are highlighted instead.
|
|
16
|
+
#
|
|
17
|
+
# The drawing itself is JavaScript, and lives in JavaScript: marker.js beside
|
|
18
|
+
# this file. Asked to draw nothing, it takes off what it drew last time, which
|
|
19
|
+
# is how the marker is removed.
|
|
20
|
+
module Marker
|
|
21
|
+
SCRIPT = File.read(File.expand_path('marker.js', __dir__)).freeze
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# Draws the marker, runs the block, and takes it off again -- including when
|
|
26
|
+
# the block raised, because a marker left behind would appear in the next
|
|
27
|
+
# picture, pointing at something that is no longer there.
|
|
28
|
+
def around(page, target, mode:)
|
|
29
|
+
return yield if target.nil? || !Howdoc.config.marker?
|
|
30
|
+
|
|
31
|
+
drawn = draw(page, target, mode:)
|
|
32
|
+
|
|
33
|
+
begin
|
|
34
|
+
yield
|
|
35
|
+
ensure
|
|
36
|
+
draw(page, nil, mode:) if drawn
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def draw(page, target, mode:)
|
|
41
|
+
page.execute_script(SCRIPT, target, options(mode))
|
|
42
|
+
true
|
|
43
|
+
rescue StandardError
|
|
44
|
+
# A marker is a courtesy. A page that will not take one -- a frame gone, an
|
|
45
|
+
# element detached between finding it and drawing on it -- still has a
|
|
46
|
+
# picture worth taking.
|
|
47
|
+
false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def options(mode)
|
|
51
|
+
{
|
|
52
|
+
'mode' => mode.to_s,
|
|
53
|
+
'colour' => Howdoc.config.marker_colour,
|
|
54
|
+
'dim' => Howdoc.config.marker_dim,
|
|
55
|
+
'size' => Howdoc.config.marker_size
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/howdoc/recorder.rb
CHANGED
|
@@ -54,7 +54,7 @@ module Howdoc
|
|
|
54
54
|
# Illustrates the step recorded most recently. Separate from +record+
|
|
55
55
|
# because a screenshot has to be taken while the browser is still on the
|
|
56
56
|
# page, whereas the sentence can be composed at any time.
|
|
57
|
-
def capture(page, full_page: false, no_screenshot: false)
|
|
57
|
+
def capture(page, full_page: false, no_screenshot: false, marker: nil, marker_mode: :pointer)
|
|
58
58
|
return nil if no_screenshot || !recording?
|
|
59
59
|
|
|
60
60
|
step = current.last_step || current.new_step
|
|
@@ -64,7 +64,7 @@ module Howdoc
|
|
|
64
64
|
page,
|
|
65
65
|
path: File.join(current.image_dir, filename),
|
|
66
66
|
height: Howdoc.config.screenshot_height,
|
|
67
|
-
full_page:
|
|
67
|
+
full_page:, marker:, marker_mode:
|
|
68
68
|
)
|
|
69
69
|
|
|
70
70
|
step.screenshot = filename
|
data/lib/howdoc/screenshot.rb
CHANGED
|
@@ -7,7 +7,10 @@ module Howdoc
|
|
|
7
7
|
module Screenshot
|
|
8
8
|
module_function
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
# The marker is drawn after the window has been sized and not before: a
|
|
11
|
+
# resize lays the page out again, and an arrow placed by the old layout
|
|
12
|
+
# would point at where the button used to be.
|
|
13
|
+
def capture(page, path:, height:, full_page: false, marker: nil, marker_mode: :pointer)
|
|
11
14
|
FileUtils.mkdir_p(File.dirname(path))
|
|
12
15
|
|
|
13
16
|
width, current_height = page.current_window.size
|
|
@@ -15,7 +18,10 @@ module Howdoc
|
|
|
15
18
|
|
|
16
19
|
page.current_window.resize_to(width, target) unless target == current_height
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
Marker.around(page, marker, mode: marker_mode) do
|
|
22
|
+
page.save_screenshot(path, full: true)
|
|
23
|
+
end
|
|
24
|
+
|
|
19
25
|
path
|
|
20
26
|
end
|
|
21
27
|
|
data/lib/howdoc/version.rb
CHANGED
data/lib/howdoc.rb
CHANGED
|
@@ -9,6 +9,7 @@ require_relative 'howdoc/narrator'
|
|
|
9
9
|
require_relative 'howdoc/step'
|
|
10
10
|
require_relative 'howdoc/edition'
|
|
11
11
|
require_relative 'howdoc/document'
|
|
12
|
+
require_relative 'howdoc/marker'
|
|
12
13
|
require_relative 'howdoc/screenshot'
|
|
13
14
|
require_relative 'howdoc/recorder'
|
|
14
15
|
require_relative 'howdoc/templates'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: howdoc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Priit Tark
|
|
@@ -59,6 +59,8 @@ files:
|
|
|
59
59
|
- lib/howdoc/configuration.rb
|
|
60
60
|
- lib/howdoc/document.rb
|
|
61
61
|
- lib/howdoc/edition.rb
|
|
62
|
+
- lib/howdoc/marker.js
|
|
63
|
+
- lib/howdoc/marker.rb
|
|
62
64
|
- lib/howdoc/minitest.rb
|
|
63
65
|
- lib/howdoc/narrator.rb
|
|
64
66
|
- lib/howdoc/navigation.rb
|