0
# Plugin System
1
2
Hook-based plugin architecture enabling extensible test reporting with separate hooks for end users and adapter developers. Built on pluggy, the system provides a flexible way to extend Allure functionality and integrate with different testing frameworks.
3
4
## Capabilities
5
6
### Plugin Manager
7
8
Singleton plugin manager providing access to the hook system and plugin registration.
9
10
```python { .api }
11
class plugin_manager:
12
"""
13
Singleton plugin manager built on pluggy.
14
15
Provides access to hook system for registering plugins and calling hooks.
16
Automatically registers AllureUserHooks and AllureDeveloperHooks.
17
"""
18
19
# All PluginManager methods are available via metaclass delegation
20
# Key methods include:
21
# - register(plugin): Register a plugin instance
22
# - unregister(plugin): Unregister a plugin instance
23
# - get_plugins(): Get all registered plugins
24
# - hook: Access to hook calling interface
25
26
class MetaPluginManager(type):
27
"""Metaclass managing plugin manager singleton."""
28
29
@staticmethod
30
def get_plugin_manager():
31
"""
32
Get or create the plugin manager instance.
33
34
Returns:
35
PluginManager: Configured pluggy PluginManager instance
36
"""
37
```
38
39
**Usage Examples:**
40
41
```python
42
from allure_commons import plugin_manager
43
44
# Register a custom plugin
45
class MyAllurePlugin:
46
@allure_commons.hookimpl
47
def report_result(self, result):
48
# Custom result processing
49
print(f"Test completed: {result.name}")
50
51
# Register the plugin
52
my_plugin = MyAllurePlugin()
53
plugin_manager.register(my_plugin)
54
55
# Call hooks (usually done by the framework)
56
plugin_manager.hook.report_result(result=test_result)
57
```
58
59
### Hook Specifications
60
61
Two categories of hooks: user hooks for test enhancement and developer hooks for framework integration.
62
63
### User Hooks (AllureUserHooks)
64
65
Hooks for test decoration and runtime enhancement, primarily used by test authors.
66
67
```python { .api }
68
class AllureUserHooks:
69
"""Hook specifications for end-user test enhancement."""
70
71
@hookspec
72
def decorate_as_title(self, test_title):
73
"""
74
Hook for decorating tests with titles.
75
76
Parameters:
77
- test_title (str): Title to apply to test
78
79
Returns:
80
Function decorator or None
81
"""
82
83
@hookspec
84
def add_title(self, test_title):
85
"""
86
Hook for adding titles at runtime.
87
88
Parameters:
89
- test_title (str): Title to add to current test
90
"""
91
92
@hookspec
93
def decorate_as_description(self, test_description):
94
"""
95
Hook for decorating tests with descriptions.
96
97
Parameters:
98
- test_description (str): Description to apply to test
99
100
Returns:
101
Function decorator or None
102
"""
103
104
@hookspec
105
def add_description(self, test_description):
106
"""
107
Hook for adding descriptions at runtime.
108
109
Parameters:
110
- test_description (str): Description to add to current test
111
"""
112
113
@hookspec
114
def decorate_as_description_html(self, test_description_html):
115
"""
116
Hook for decorating tests with HTML descriptions.
117
118
Parameters:
119
- test_description_html (str): HTML description to apply
120
121
Returns:
122
Function decorator or None
123
"""
124
125
@hookspec
126
def add_description_html(self, test_description_html):
127
"""
128
Hook for adding HTML descriptions at runtime.
129
130
Parameters:
131
- test_description_html (str): HTML description to add
132
"""
133
134
@hookspec
135
def decorate_as_label(self, label_type, labels):
136
"""
137
Hook for decorating tests with labels.
138
139
Parameters:
140
- label_type (str): Type of label
141
- labels (tuple): Label values
142
143
Returns:
144
Function decorator or None
145
"""
146
147
@hookspec
148
def add_label(self, label_type, labels):
149
"""
150
Hook for adding labels at runtime.
151
152
Parameters:
153
- label_type (str): Type of label
154
- labels (tuple): Label values
155
"""
156
157
@hookspec
158
def decorate_as_link(self, url, link_type, name):
159
"""
160
Hook for decorating tests with links.
161
162
Parameters:
163
- url (str): Link URL
164
- link_type (str): Type of link
165
- name (str): Display name for link
166
167
Returns:
168
Function decorator or None
169
"""
170
171
@hookspec
172
def add_link(self, url, link_type, name):
173
"""
174
Hook for adding links at runtime.
175
176
Parameters:
177
- url (str): Link URL
178
- link_type (str): Type of link
179
- name (str): Display name for link
180
"""
181
182
@hookspec
183
def add_parameter(self, name, value, excluded, mode):
184
"""
185
Hook for adding parameters at runtime.
186
187
Parameters:
188
- name (str): Parameter name
189
- value: Parameter value
190
- excluded (bool): Whether to exclude from display
191
- mode (ParameterMode): Display mode
192
"""
193
194
@hookspec
195
def start_step(self, uuid, title, params):
196
"""
197
Hook for starting test steps.
198
199
Parameters:
200
- uuid (str): Step UUID
201
- title (str): Step title
202
- params (dict): Step parameters
203
"""
204
205
@hookspec
206
def stop_step(self, uuid, exc_type, exc_val, exc_tb):
207
"""
208
Hook for stopping test steps.
209
210
Parameters:
211
- uuid (str): Step UUID
212
- exc_type: Exception type if step failed
213
- exc_val: Exception value if step failed
214
- exc_tb: Exception traceback if step failed
215
"""
216
217
@hookspec
218
def attach_data(self, body, name, attachment_type, extension):
219
"""
220
Hook for attaching data to tests.
221
222
Parameters:
223
- body (str or bytes): Data to attach
224
- name (str): Attachment display name
225
- attachment_type: MIME type or AttachmentType
226
- extension (str): File extension
227
"""
228
229
@hookspec
230
def attach_file(self, source, name, attachment_type, extension):
231
"""
232
Hook for attaching files to tests.
233
234
Parameters:
235
- source (str): Path to source file
236
- name (str): Attachment display name
237
- attachment_type: MIME type or AttachmentType
238
- extension (str): File extension
239
"""
240
```
241
242
### Developer Hooks (AllureDeveloperHooks)
243
244
Hooks for framework integration and test lifecycle management, used by testing framework adapters.
245
246
```python { .api }
247
class AllureDeveloperHooks:
248
"""Hook specifications for framework adapter developers."""
249
250
@hookspec
251
def start_fixture(self, parent_uuid, uuid, name, parameters):
252
"""
253
Hook for starting fixture execution.
254
255
Parameters:
256
- parent_uuid (str): Parent container UUID
257
- uuid (str): Fixture UUID
258
- name (str): Fixture name
259
- parameters (dict): Fixture parameters
260
"""
261
262
@hookspec
263
def stop_fixture(self, parent_uuid, uuid, name, exc_type, exc_val, exc_tb):
264
"""
265
Hook for stopping fixture execution.
266
267
Parameters:
268
- parent_uuid (str): Parent container UUID
269
- uuid (str): Fixture UUID
270
- name (str): Fixture name
271
- exc_type: Exception type if fixture failed
272
- exc_val: Exception value if fixture failed
273
- exc_tb: Exception traceback if fixture failed
274
"""
275
276
@hookspec
277
def start_test(self, parent_uuid, uuid, name, parameters, context):
278
"""
279
Hook for starting test execution.
280
281
Parameters:
282
- parent_uuid (str): Parent container UUID
283
- uuid (str): Test UUID
284
- name (str): Test name
285
- parameters (dict): Test parameters
286
- context (dict): Test execution context
287
"""
288
289
@hookspec
290
def stop_test(self, parent_uuid, uuid, name, context, exc_type, exc_val, exc_tb):
291
"""
292
Hook for stopping test execution.
293
294
Parameters:
295
- parent_uuid (str): Parent container UUID
296
- uuid (str): Test UUID
297
- name (str): Test name
298
- context (dict): Test execution context
299
- exc_type: Exception type if test failed
300
- exc_val: Exception value if test failed
301
- exc_tb: Exception traceback if test failed
302
"""
303
304
@hookspec
305
def report_result(self, result):
306
"""
307
Hook for reporting test results.
308
309
Parameters:
310
- result (TestResult): Complete test result object
311
"""
312
313
@hookspec
314
def report_container(self, container):
315
"""
316
Hook for reporting test containers.
317
318
Parameters:
319
- container (TestResultContainer): Test container object
320
"""
321
322
@hookspec
323
def report_attached_file(self, source, file_name):
324
"""
325
Hook for reporting attached files.
326
327
Parameters:
328
- source (str): Source file path
329
- file_name (str): Destination file name
330
"""
331
332
@hookspec
333
def report_attached_data(self, body, file_name):
334
"""
335
Hook for reporting attached data.
336
337
Parameters:
338
- body (str or bytes): Data content
339
- file_name (str): Destination file name
340
"""
341
```
342
343
### Hook Implementation
344
345
Decorators and markers for implementing hooks in plugins.
346
347
```python { .api }
348
# Hook specification marker
349
hookspec: HookspecMarker
350
351
# Hook implementation marker
352
hookimpl: HookimplMarker
353
```
354
355
**Usage Examples:**
356
357
```python
358
from allure_commons import hookimpl
359
360
class CustomReporter:
361
"""Custom reporter plugin example."""
362
363
@hookimpl
364
def report_result(self, result):
365
"""Custom test result processing."""
366
print(f"Test {result.name}: {result.status}")
367
368
# Send to external system
369
send_to_dashboard({
370
'test_name': result.name,
371
'status': result.status,
372
'duration': result.stop - result.start
373
})
374
375
@hookimpl
376
def report_container(self, container):
377
"""Custom container processing."""
378
print(f"Container {container.name} completed with {len(container.children)} tests")
379
380
@hookimpl
381
def attach_data(self, body, name, attachment_type, extension):
382
"""Custom attachment processing."""
383
if attachment_type == "application/json":
384
# Parse and validate JSON attachments
385
try:
386
data = json.loads(body)
387
print(f"Valid JSON attachment: {name}")
388
except json.JSONDecodeError:
389
print(f"Invalid JSON attachment: {name}")
390
391
class TestFrameworkAdapter:
392
"""Example testing framework adapter."""
393
394
@hookimpl
395
def start_test(self, parent_uuid, uuid, name, parameters, context):
396
"""Handle test start for framework integration."""
397
framework_context = context.get('framework')
398
if framework_context:
399
# Framework-specific test setup
400
framework_context.setup_test_environment()
401
402
@hookimpl
403
def stop_test(self, parent_uuid, uuid, name, context, exc_type, exc_val, exc_tb):
404
"""Handle test completion for framework integration."""
405
framework_context = context.get('framework')
406
if framework_context:
407
# Framework-specific cleanup
408
framework_context.cleanup_test_environment()
409
410
# Handle test failures
411
if exc_type:
412
print(f"Test {name} failed: {exc_val}")
413
414
# Register plugins
415
plugin_manager.register(CustomReporter())
416
plugin_manager.register(TestFrameworkAdapter())
417
```
418
419
### Advanced Plugin Patterns
420
421
#### Multi-Reporter Setup
422
423
```python
424
from allure_commons.logger import AllureFileLogger, AllureMemoryLogger
425
426
class DatabaseReporter:
427
"""Store results in database."""
428
429
@hookimpl
430
def report_result(self, result):
431
# Store in database
432
database.store_test_result({
433
'uuid': result.uuid,
434
'name': result.name,
435
'status': result.status,
436
'labels': [{'name': l.name, 'value': l.value} for l in result.labels]
437
})
438
439
# Register multiple reporters
440
file_logger = AllureFileLogger("/tmp/allure-results")
441
memory_logger = AllureMemoryLogger() # Store results in memory
442
db_reporter = DatabaseReporter()
443
444
plugin_manager.register(file_logger)
445
plugin_manager.register(memory_logger)
446
plugin_manager.register(db_reporter)
447
```
448
449
### Built-in Logger Classes
450
451
The library provides two built-in reporter implementations for common use cases.
452
453
```python { .api }
454
class AllureFileLogger:
455
"""
456
File-based logger that writes results to JSON files.
457
458
Writes test results, containers, and attachments to a specified directory
459
in the standard Allure JSON format.
460
"""
461
462
def __init__(self, report_dir, clean=False):
463
"""
464
Initialize file logger.
465
466
Parameters:
467
- report_dir (str): Directory to write result files
468
- clean (bool): Whether to clean existing files on initialization
469
"""
470
471
@hookimpl
472
def report_result(self, result):
473
"""Write test result to JSON file."""
474
475
@hookimpl
476
def report_container(self, container):
477
"""Write test container to JSON file."""
478
479
@hookimpl
480
def report_attached_file(self, source, file_name):
481
"""Copy attached file to results directory."""
482
483
@hookimpl
484
def report_attached_data(self, body, file_name):
485
"""Write attached data to file in results directory."""
486
487
class AllureMemoryLogger:
488
"""
489
Memory-based logger that stores results in memory for programmatic access.
490
491
Useful for testing, debugging, or custom result processing.
492
"""
493
494
def __init__(self):
495
"""Initialize memory logger with empty collections."""
496
497
# Attributes:
498
# - test_cases (list): List of test result dictionaries
499
# - test_containers (list): List of container dictionaries
500
# - attachments (dict): Dictionary mapping file names to content
501
502
@hookimpl
503
def report_result(self, result):
504
"""Store test result in memory."""
505
506
@hookimpl
507
def report_container(self, container):
508
"""Store test container in memory."""
509
510
@hookimpl
511
def report_attached_file(self, source, file_name):
512
"""Store file path reference in memory."""
513
514
@hookimpl
515
def report_attached_data(self, body, file_name):
516
"""Store attached data in memory."""
517
```
518
519
**Usage Examples:**
520
521
```python
522
from allure_commons.logger import AllureFileLogger, AllureMemoryLogger
523
524
# File-based logging
525
file_logger = AllureFileLogger("/tmp/allure-results", clean=True)
526
plugin_manager.register(file_logger)
527
528
# Memory-based logging for analysis
529
memory_logger = AllureMemoryLogger()
530
plugin_manager.register(memory_logger)
531
532
# After test execution, access results
533
print(f"Executed {len(memory_logger.test_cases)} tests")
534
print(f"Created {len(memory_logger.test_containers)} containers")
535
print(f"Generated {len(memory_logger.attachments)} attachments")
536
537
# Process results programmatically
538
for test_case in memory_logger.test_cases:
539
if test_case['status'] == 'failed':
540
print(f"Failed test: {test_case['name']}")
541
```
542
543
#### Conditional Hook Execution
544
545
```python
546
class ConditionalReporter:
547
"""Reporter that executes based on conditions."""
548
549
def __init__(self, enabled=True, filter_status=None):
550
self.enabled = enabled
551
self.filter_status = filter_status
552
553
@hookimpl
554
def report_result(self, result):
555
if not self.enabled:
556
return
557
558
if self.filter_status and result.status != self.filter_status:
559
return
560
561
# Process filtered results
562
self.process_result(result)
563
564
# Only report failed tests
565
failed_reporter = ConditionalReporter(filter_status='failed')
566
plugin_manager.register(failed_reporter)
567
```