Skip to content
This repository was archived by the owner on May 13, 2022. It is now read-only.

Commit 12d774d

Browse files
Merge pull request #228 from golemhq/feature/test-hooks
add test hooks for test file: before_test, before_each, after_each, a…
2 parents bde549f + f4e9a1e commit 12d774d

File tree

11 files changed

+607
-376
lines changed

11 files changed

+607
-376
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Changed
66

77
- Added JSON as data source, internal data uses a Python code editor, test_data setting is deprecated [#225](https://github.com/golemhq/golem/issues/225)
8+
- Added 4 test hooks: before_test, before_each, after_each, after_test [#227](https://github.com/golemhq/golem/issues/227)
89

910
## [0.10.1] - 2021-07-30
1011

golem/core/test.py

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,15 @@ def _print_extra_blank_line():
138138
test_.append('')
139139
extra_blank_line = False
140140

141+
def _order_hooks(test_hooks):
142+
"""Put the test hooks in a predefined order"""
143+
ordered = {}
144+
order_list = ['before_test', 'setup', 'before_each', 'after_each', 'after_test', 'teardown']
145+
for hook_name in order_list:
146+
if hook_name in test_hooks:
147+
ordered[hook_name] = test_hooks[hook_name]
148+
return ordered
149+
141150
path = Test(project, test_name).path
142151
settings = settings_manager.get_project_settings(project)
143152

@@ -196,11 +205,14 @@ def _print_extra_blank_line():
196205
skip = "'{}'".format(skip)
197206
test_.append('skip = {}'.format(skip))
198207

199-
if steps['setup']:
200-
test_.append('')
201-
test_.append('')
202-
test_.append('def setup(data):')
203-
test_.append(_format_steps(steps['setup']))
208+
if steps['hooks']:
209+
ordered_hooks = _order_hooks(steps['hooks'])
210+
for hook, hook_steps in ordered_hooks.items():
211+
if hook_steps:
212+
test_.append('')
213+
test_.append('')
214+
test_.append(f'def {hook}(data):')
215+
test_.append(_format_steps(hook_steps))
204216

205217
for test_function_name, test_function_steps in steps['tests'].items():
206218
test_.append('')
@@ -211,12 +223,6 @@ def _print_extra_blank_line():
211223
else:
212224
test_.append(' pass')
213225

214-
if steps['teardown']:
215-
test_.append('')
216-
test_.append('')
217-
test_.append('def teardown(data):')
218-
test_.append(_format_steps(steps['teardown']))
219-
220226
test_.append('')
221227

222228
with open(path, 'w', encoding='utf-8') as f:
@@ -284,22 +290,6 @@ def pages(self):
284290
imported_pages = test_parser.parse_imported_pages(self.code)
285291
return page_list + imported_pages
286292

287-
@property
288-
def setup_steps(self):
289-
setup_function = getattr(self.get_module(), 'setup', None)
290-
if setup_function:
291-
return test_parser.parse_function_steps(setup_function)
292-
else:
293-
return None
294-
295-
@property
296-
def teardown_steps(self):
297-
teardown_function = getattr(self.get_module(), 'teardown', None)
298-
if teardown_function:
299-
return test_parser.parse_function_steps(teardown_function)
300-
else:
301-
return None
302-
303293
@property
304294
def test_functions(self):
305295
"""Dictionary of parsed steps of each test function"""
@@ -319,6 +309,37 @@ def test_function_list(self):
319309
local_function_names = parsing_utils.top_level_functions(ast_module_node)
320310
return [f for f in local_function_names if f.startswith('test')]
321311

312+
@property
313+
def test_hooks(self):
314+
"""Dictionary of parsed steps of each test hook function"""
315+
hooks = {}
316+
test_hooks = self.test_hook_list
317+
for hook_name in test_hooks:
318+
h = getattr(self.get_module(), hook_name)
319+
# TODO setup / teardown are deprecated, returned as before_test/after_test
320+
if hook_name == 'setup':
321+
hook_name = 'before_test'
322+
if hook_name == 'teardown':
323+
hook_name = 'after_test'
324+
hooks[hook_name] = test_parser.parse_function_steps(h)
325+
326+
from collections import OrderedDict
327+
return OrderedDict(hooks)
328+
329+
@property
330+
def test_hook_list(self):
331+
"""List of test hook functions"""
332+
ast_module_node = parsing_utils.ast_parse_file(self.path)
333+
local_function_names = parsing_utils.top_level_functions(ast_module_node)
334+
test_hook_names = ['setup', 'teardown', 'before_test', 'before_each', 'after_each', 'after_test']
335+
test_hooks = [f for f in local_function_names if f in test_hook_names]
336+
# TODO: setup / teardown are deprecated
337+
if 'setup' in test_hooks and 'before_test' in test_hooks:
338+
test_hooks.remove('setup')
339+
if 'teardown' in test_hooks and 'after_test' in test_hooks:
340+
test_hooks.remove('teardown')
341+
return test_hooks
342+
322343
@property
323344
def skip(self):
324345
return getattr(self.get_module(), 'skip', False)
@@ -343,10 +364,10 @@ def components(self):
343364
'pages': self.pages,
344365
'tags': self.tags,
345366
'skip': self.skip,
346-
'setup_steps': self.setup_steps,
347-
'teardown_steps': self.teardown_steps,
348367
'test_functions': self.test_functions,
349368
'test_function_list': self.test_function_list,
369+
'test_hooks': self.test_hooks,
370+
'test_hook_list': self.test_hook_list,
350371
'code': self.code
351372
}
352373
return components

golem/execution_runner/execution_runner.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ def initialize_reports_for_test_files(project_name, test_sets):
8686
# will not be initialized
8787
if s.name in test_functions_cache:
8888
test_report.initialize_test_file_report(s.name, test_functions_cache[s.name],
89-
s.set_name, test_file_reportdir)
89+
s.set_name, test_file_reportdir, s.env,
90+
s.browser['name'])
9091

9192

9293
class ExecutionRunner:

golem/gui/static/js/test.js

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ var Test = new function(){
88
this.importedPages = [];
99
this.unsavedChanges = false;
1010

11-
this.initialize = function(file, importedPages, setupSteps, teardownSteps, testFunctions){
11+
this.initialize = function(file, importedPages, testHooks, testFunctions){
1212

1313
this.file = file;
1414

@@ -18,15 +18,12 @@ var Test = new function(){
1818
});
1919
Test.getAllProjectPages();
2020
Test.getGolemActions();
21-
Test.renderSectionSteps(Test.Utils.stepSection('setup'), setupSteps);
22-
Test.renderSectionSteps(Test.Utils.stepSection('teardown'), teardownSteps);
21+
Test.addTestHooks(testHooks);
2322
Test.addTestFunctions(testFunctions);
2423
Test.refreshActionInputsAutocomplete();
2524
Test.refreshValueInputsAutocomplete();
26-
$('#pageModal').on('hidden.bs.modal', function(){
27-
Test.importedPages.forEach(function(page){
28-
Test.getPageContents(page.name)
29-
})
25+
$('#pageModal').on('hidden.bs.modal', function() {
26+
Test.importedPages.forEach((page) => Test.getPageContents(page.name))
3027
});
3128
Test.Utils.watchForUnsavedChanges();
3229
Test.Utils.startSortableSteps();
@@ -47,38 +44,60 @@ var Test = new function(){
4744
}
4845
}
4946

50-
this.addTestFunction = function(testName, testSteps) {
51-
testSteps = testSteps || [];
52-
let testFunctionContainer = $('#testFunctions');
47+
this.addTestFunction = function(functionName, steps) {
48+
this.addXFunction(functionName, steps, $('#testFunctions'), true);
49+
}
50+
51+
this.addTestHooks = function(testHooks) {
52+
let orderedTestHooks = Test.Hooks.orderTestHooks(testHooks);
53+
54+
for (const testHook in orderedTestHooks) {
55+
Test.addTestHook(testHook, testHooks[testHook]);
56+
}
57+
}
58+
59+
this.addTestHook = function(hookName, steps) {
60+
Test.Hooks.addHook(hookName, steps);
61+
}
62+
63+
this.addXFunction = function(functionName, testSteps, container, isTestFunction) {
64+
// only test functions have on click to modify test function name
65+
// test hooks names cannot be modified
66+
if(isTestFunction) {
67+
functionNameOnClick = 'Test.Utils.startTestFunctionInlineNameEdition(this)';
68+
removeIconOnClick = 'Test.Utils.deleteTestFunction(this)';
69+
} else {
70+
functionNameOnClick = '';
71+
removeIconOnClick = 'Test.Hooks.deleteHook(this)';
72+
}
73+
5374
let testFunctionTemplate = $(`
5475
<div class='test-function function'>
5576
<div class='testFunctionNameContainer'>
5677
<h4 class="testFunctionNameContainer">
57-
<span class="test-function-name" onclick="Test.Utils.startTestFunctionInlineNameEdition(this)">${testName}</span>
78+
<span class="test-function-name" onclick="${functionNameOnClick}">${functionName}</span>
5879
<span class="test-function-name-input" style="display: none">
5980
<input type="text">
6081
</span>
6182
</h4>
6283
<div class="inline-remove-icon">
63-
<a href="javascript:void(0)" onclick="Test.Utils.deleteTestFunction(this)">
84+
<a href="javascript:void(0)" onclick="${removeIconOnClick}">
6485
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
6586
</a>
6687
</div>
6788
</div>
6889
<div class='steps'></div>
6990
<button class='btn btn-default btn-sm add-step' style='margin-left: 21px;' onclick="Test.addStepInputToThis(this);">
7091
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span></button>
71-
</div>
72-
<div style='height: 10px'></div>
73-
`);
92+
</div>`);
7493

7594
let stepContainer = testFunctionTemplate.find('.steps');
7695
if(testSteps.length) {
7796
Test.renderSectionSteps(stepContainer, testSteps);
7897
} else {
7998
Test.addStepInputToThis(stepContainer);
8099
}
81-
testFunctionContainer.append(testFunctionTemplate);
100+
container.append(testFunctionTemplate);
82101
Test.refreshActionInputsAutocomplete();
83102
Test.refreshValueInputsAutocomplete();
84103
}
@@ -133,7 +152,7 @@ var Test = new function(){
133152

134153
this.addNewTestFunction = function() {
135154
let callback = (testName) => {
136-
Test.addTestFunction(testName)
155+
Test.addTestFunction(testName, [])
137156
}
138157
let validationCallback = (testName) => {
139158
return Test.Utils.validateTestFunctionName(testName);
@@ -299,7 +318,7 @@ var Test = new function(){
299318
Main.TestRunner.runTest(this.file.project, Test.file.fullName);
300319
}
301320

302-
this.save = function(config){
321+
this.save = function(config) {
303322
runAfter = config.runAfter || false;
304323
let description = $("#description").val();
305324
let pageObjects = Test.importedPages.map(x => x.name);
@@ -480,10 +499,38 @@ var Test = new function(){
480499
return template
481500
}
482501

502+
this.Hooks = new function() {
483503

484-
this.Utils = new function(){
504+
this.orderTestHooks = function(hooks) {
505+
ordered = {}
506+
orderList = ['before_test', 'setup', 'before_each', 'after_each', 'after_test', 'teardown']
507+
for (hookName of orderList) {
508+
if(hookName in hooks) {
509+
ordered[hookName] = hooks[hookName]
510+
}
511+
}
512+
return ordered
513+
}
485514

486-
this.getNotImportedPages = function(){
515+
this.addHook = function(name, steps) {
516+
steps = steps || [];
517+
Test.addXFunction(name, steps, $('#testHooks'), false);
518+
$(`#hookSelector a[hook-name='${name}']`).hide();
519+
}
520+
521+
this.deleteHook = function(elem) {
522+
let hookName = $(elem).closest('.test-function').find('h4 span').html();
523+
Main.Utils.displayConfirmModal('Delete Test Hook?', '', () => {
524+
$(elem).closest('.test-function').remove();
525+
Test.unsavedChanges = true;
526+
$(`#hookSelector a[hook-name='${hookName}']`).show();
527+
});
528+
}
529+
}
530+
531+
this.Utils = new function() {
532+
533+
this.getNotImportedPages = function() {
487534
let notImported = [];
488535
let importedPagesNames = Test.importedPages.map(x => x.name);
489536
Test.allPages.forEach(function(page){
@@ -593,10 +640,13 @@ var Test = new function(){
593640

594641
this.getSteps = function() {
595642
let steps = {
596-
setup: Test.Utils.parseSteps($("#setupSteps .step")),
643+
hooks: {},
597644
tests: {},
598-
teardown: Test.Utils.parseSteps($("#teardownSteps .step"))
599645
}
646+
$('#testHooks > .test-function').each(function() {
647+
let hookName = $(this).find('span.test-function-name').html().trim();
648+
steps.hooks[hookName] = Test.Utils.parseSteps($(this).find('.step'))
649+
})
600650
$('#testFunctions > .test-function').each(function() {
601651
let testName = $(this).find('span.test-function-name').html().trim();
602652
steps.tests[testName] = Test.Utils.parseSteps($(this).find('.step'))
@@ -659,7 +709,7 @@ var Test = new function(){
659709
}
660710

661711
this.deleteTestFunction = function(elem) {
662-
Main.Utils.displayConfirmModal('Delete Test', 'Delete test?', () => {
712+
Main.Utils.displayConfirmModal('Delete Test?', '', () => {
663713
$(elem).closest('.test-function').remove();
664714
Test.unsavedChanges = true;
665715
});

0 commit comments

Comments
 (0)