|
8 | 8 | """ |
9 | 9 |
|
10 | 10 | # imported methods start with underscore (_) so they don't get imported into robot files as keywords |
| 11 | +import datetime as _datetime |
11 | 12 | from os.path import join as _pJoin |
12 | 13 | import tempfile as _tempfile |
13 | 14 | from typing import Optional as _Optional |
|
18 | 19 | from SystemTestSpy.windows import ( |
19 | 20 | CloseWindow, |
20 | 21 | GetWindowWithTitle, |
21 | | - GetForegroundWindowTitle, |
22 | 22 | Window, |
23 | 23 | ) |
24 | 24 | import re |
|
29 | 29 | from robot.libraries.Process import Process as _ProcessLib |
30 | 30 | from AssertsLib import AssertsLib as _AssertsLib |
31 | 31 | import NvdaLib as _NvdaLib |
| 32 | +import WindowsLib as _WindowsLib |
32 | 33 |
|
33 | 34 | builtIn: BuiltIn = BuiltIn() |
34 | 35 | opSys: _OpSysLib = _getLib('OperatingSystem') |
35 | 36 | process: _ProcessLib = _getLib('Process') |
36 | 37 | assertsLib: _AssertsLib = _getLib('AssertsLib') |
| 38 | +windowsLib: _WindowsLib = _getLib('WindowsLib') |
37 | 39 |
|
38 | 40 |
|
39 | 41 | # In Robot libraries, class name must match the name of the module. Use caps for both. |
@@ -159,88 +161,88 @@ def _waitForStartMarker(self) -> bool: |
159 | 161 | @return: False on failure |
160 | 162 | """ |
161 | 163 | spy = _NvdaLib.getSpyLib() |
162 | | - spy.emulateKeyPress('alt+d') # focus the address bar, chrome shortcut |
163 | 164 | spy.wait_for_speech_to_finish() |
164 | | - addressSpeechIndex = spy.get_last_speech_index() |
| 165 | + expectedAddressBarSpeech = "Address and search bar" |
| 166 | + moveToAddressBarSpeech = _NvdaLib.getSpeechAfterKey('nvda+tab') # report current focus. |
| 167 | + if expectedAddressBarSpeech not in moveToAddressBarSpeech: |
| 168 | + moveToAddressBarSpeech = _NvdaLib.getSpeechAfterKey('alt+d') # focus the address bar, chrome shortcut |
| 169 | + if expectedAddressBarSpeech not in moveToAddressBarSpeech: |
| 170 | + builtIn.log( |
| 171 | + f"Didn't read '{expectedAddressBarSpeech}' after alt+d, instead got: {moveToAddressBarSpeech}" |
| 172 | + ) |
| 173 | + return False |
165 | 174 |
|
166 | | - spy.emulateKeyPress('control+F6') # focus web content, chrome shortcut. |
167 | | - spy.wait_for_speech_to_finish() |
168 | | - afterControlF6Speech = spy.get_speech_at_index_until_now(addressSpeechIndex) |
169 | | - if f"document\n{ChromeLib._beforeMarker}" not in afterControlF6Speech: |
170 | | - builtIn.log(afterControlF6Speech, level="DEBUG") |
| 175 | + afterControlF6Speech = _NvdaLib.getSpeechAfterKey('control+F6') # focus web content, chrome shortcut. |
| 176 | + documentDescriptor = f"document\n{ChromeLib._beforeMarker}" |
| 177 | + if documentDescriptor not in afterControlF6Speech: |
| 178 | + builtIn.log( |
| 179 | + f"Didn't get '{documentDescriptor}' after moving to document, instead got: {afterControlF6Speech}" |
| 180 | + ) |
171 | 181 | return False |
172 | 182 |
|
173 | | - spy.emulateKeyPress('control+home') # ensure we start at the top of the document |
174 | | - controlHomeSpeechIndex = spy.get_last_speech_index() |
175 | | - spy.emulateKeyPress('numpad8') # report current line |
| 183 | + # ensure we start at the top of the document |
| 184 | + _NvdaLib.getSpeechAfterKey('control+home') |
176 | 185 | spy.wait_for_speech_to_finish() |
177 | | - afterNumPad8Speech = spy.get_speech_at_index_until_now(controlHomeSpeechIndex) |
| 186 | + |
| 187 | + afterNumPad8Speech = _NvdaLib.getSpeechAfterKey('numpad8') # report current line |
178 | 188 | if ChromeLib._beforeMarker not in afterNumPad8Speech: |
179 | | - builtIn.log(afterNumPad8Speech, level="DEBUG") |
| 189 | + builtIn.log( |
| 190 | + f"Didn't get {ChromeLib._beforeMarker} after reporting the current line" |
| 191 | + f", instead got: {afterNumPad8Speech}" |
| 192 | + ) |
180 | 193 | return False |
181 | 194 | return True |
182 | 195 |
|
183 | | - def toggleFocusChrome(self) -> None: |
184 | | - """Remove focus, then refocus chrome |
185 | | - Attempt to work around NVDA missing focus / foreground events when chrome first opens. |
186 | | - Forcing chrome to send another foreground event by focusing the desktop, then using alt+tab to return |
187 | | - chrome to the foreground. |
188 | | - @remarks If another application raises to the foreground after chrome, this approach won't resolve that |
189 | | - situation. |
190 | | - We don't have evidence that another application taking focus is a cause of failure yet. |
191 | | - """ |
192 | | - spy = _NvdaLib.getSpyLib() |
193 | | - spy.emulateKeyPress('windows+d') |
194 | | - _blockUntilConditionMet( |
195 | | - giveUpAfterSeconds=5, |
196 | | - getValue=GetForegroundWindowTitle, |
197 | | - shouldStopEvaluator=lambda _title: self.chromeWindow.title != _title, |
198 | | - errorMessage="Chrome didn't lose focus" |
199 | | - ) |
200 | | - spy.emulateKeyPress('alt+tab') |
201 | | - _blockUntilConditionMet( |
202 | | - giveUpAfterSeconds=5, |
203 | | - getValue=GetForegroundWindowTitle, |
204 | | - shouldStopEvaluator=lambda _title: self.chromeWindow.title == _title, |
205 | | - errorMessage="Chrome didn't gain focus" |
| 196 | + def canChromeTitleBeReported(self, chromeTitleSpeechPattern: re.Pattern) -> bool: |
| 197 | + speech = _NvdaLib.getSpeechAfterKey('NVDA+t') |
| 198 | + return bool( |
| 199 | + chromeTitleSpeechPattern.search(speech) |
206 | 200 | ) |
207 | | - spy.wait_for_speech_to_finish() |
208 | | - |
209 | | - def ensureChromeTitleCanBeReported(self, applicationTitle: str) -> int: |
210 | | - spy = _NvdaLib.getSpyLib() |
211 | | - afterFocusToggleIndex = spy.get_last_speech_index() |
212 | | - spy.emulateKeyPress('NVDA+t') |
213 | | - appTitleIndex = spy.wait_for_specific_speech(applicationTitle, afterIndex=afterFocusToggleIndex) |
214 | | - return appTitleIndex |
215 | 201 |
|
216 | | - def prepareChrome(self, testCase: str, _doToggleFocus: bool = False) -> None: |
| 202 | + def prepareChrome(self, testCase: str, _alwaysDoToggleFocus: bool = False) -> None: |
217 | 203 | """ |
218 | 204 | Starts Chrome opening a file containing the HTML sample |
219 | 205 | @param testCase - The HTML sample to test. |
220 | | - @param _doToggleFocus - When True, Chrome will be intentionally de-focused and re-focused |
| 206 | + @param _alwaysDoToggleFocus - When True, Chrome will be intentionally de-focused and re-focused |
221 | 207 | """ |
| 208 | + testCase = testCase + ( |
| 209 | + "\n<!-- " # new line, start a HTML comment |
| 210 | + "Sample generation time, to ensure that the test case title is reproducibly unique purely from" |
| 211 | + " this test case string: \n" |
| 212 | + f"{ _datetime.datetime.now().isoformat()} " |
| 213 | + f" -->" # end HTML comment |
| 214 | + ) |
222 | 215 | spy = _NvdaLib.getSpyLib() |
223 | 216 | _chromeLib: "ChromeLib" = _getLib('ChromeLib') # using the lib gives automatic 'keyword' logging. |
224 | 217 | path = self._writeTestFile(testCase) |
225 | 218 |
|
226 | 219 | spy.wait_for_speech_to_finish() |
227 | | - lastSpeechIndex = spy.get_last_speech_index() |
228 | 220 | _chromeLib.start_chrome(path, testCase) |
| 221 | + windowsLib.logForegroundWindowTitle() |
| 222 | + |
229 | 223 | applicationTitle = ChromeLib.getUniqueTestCaseTitle(testCase) |
| 224 | + # application title will be something like "NVDA Browser Test Case (499078752)" |
| 225 | + # the parentheses could be escaped, instead we can just replace them with "match any char". |
| 226 | + patternSafeTitleString = applicationTitle.replace('(', '.').replace(')', '.') |
| 227 | + chromeTitleSpeechPattern = re.compile(patternSafeTitleString) |
230 | 228 |
|
231 | | - _chromeLib.ensureChromeTitleCanBeReported(applicationTitle) |
232 | | - spy.wait_for_speech_to_finish() |
| 229 | + if ( |
| 230 | + _alwaysDoToggleFocus # may work around focus/foreground event missed issues for tests. |
| 231 | + or not _chromeLib.canChromeTitleBeReported(chromeTitleSpeechPattern) |
| 232 | + ): |
| 233 | + windowsLib.taskSwitchToItemMatching(targetWindowNamePattern=chromeTitleSpeechPattern) |
| 234 | + windowsLib.logForegroundWindowTitle() |
233 | 235 |
|
234 | | - if _doToggleFocus: # may work around focus/foreground event missed issues for tests. |
235 | | - _chromeLib.toggleFocusChrome() |
236 | | - spy.wait_for_speech_to_finish() |
| 236 | + if not _chromeLib.canChromeTitleBeReported(chromeTitleSpeechPattern): |
| 237 | + raise AssertionError("NVDA unable to report chrome title") |
| 238 | + spy.wait_for_speech_to_finish() |
237 | 239 |
|
238 | 240 | if not self._waitForStartMarker(): |
239 | 241 | builtIn.fail( |
240 | 242 | "Unable to locate 'before sample' marker." |
241 | 243 | " See NVDA log for full speech." |
242 | 244 | ) |
243 | | - # Move to the loading status line, and wait fore it to become complete |
| 245 | + # Move to the loading status line, and wait for it to become complete |
244 | 246 | # the page has fully loaded. |
245 | 247 | spy.emulateKeyPress('downArrow') |
246 | 248 | for x in range(10): |
|
0 commit comments