@@ -46,6 +46,34 @@ def get_launch_cwd(self, *args):
4646 pass
4747
4848
49+ class IncrementalLineReader (object ):
50+ """
51+ Line reader (e.g. jsonl)
52+ """
53+
54+ def __init__ (self , parent_logger , filename ):
55+ self .log = parent_logger .getChild (self .__class__ .__name__ )
56+ self .partial_buffer = ""
57+ self .file = FileReader (filename = filename , parent_logger = self .log )
58+ self .read_speed = 1024 * 1024
59+
60+ def read (self , last_pass = False ):
61+ """
62+ read data from file
63+ yield one complete row (ending with \n )
64+ :type last_pass: bool
65+ """
66+ lines = self .file .get_lines (size = self .read_speed , last_pass = last_pass )
67+
68+ for line in lines :
69+ if not line .endswith ("\n " ):
70+ self .partial_buffer += line
71+ continue
72+
73+ line = "%s%s" % (self .partial_buffer , line )
74+ self .partial_buffer = ""
75+ yield line
76+
4977class PlaywrightTester (JavaScriptExecutor ):
5078
5179 """
@@ -67,7 +95,7 @@ def prepare(self):
6795 self .script = self .get_script_path ()
6896 if not self .script :
6997 raise TaurusConfigError ("Script not passed to runner %s" % self )
70- self .reader = PlaywrightLogReader (self .engine .artifacts_dir + "/playwright.out " , self .log )
98+ self .reader = PlaywrightLogReader (self .engine .artifacts_dir + "/taurus- playwright-reporter.jsonl " , self .log )
7199 if isinstance (self .engine .aggregator , ConsolidatingAggregator ):
72100 self .engine .aggregator .add_underling (self .reader )
73101
@@ -80,10 +108,11 @@ def install_required_tools(self):
80108
81109 node_npx_module = self ._get_tool (NodeNPXModule , tools_dir = self .get_launch_cwd (), node_tool = self .node , npm_tool = self .npm )
82110 playwright = self ._get_tool (PLAYWRIGHT , tools_dir = self .get_launch_cwd ())
111+ playwright_reporter = self ._get_tool (PlaywrightCustomReporter , tools_dir = self .get_launch_cwd (), node_tool = self .node , npm_tool = self .npm )
83112
84113 npm_all_packages = self ._get_tool (NPMModuleInstaller ,node_tool = self .node , npm_tool = self .npm , tools_dir = self .get_launch_cwd ())
85114
86- tools = [tcl_lib , self .node , self .npm , node_npx_module , npm_all_packages , playwright ]
115+ tools = [tcl_lib , self .node , self .npm , node_npx_module , npm_all_packages , playwright , playwright_reporter ]
87116 self ._check_tools (tools )
88117
89118 def get_launch_cmdline (self , * args ):
@@ -100,22 +129,37 @@ def startup(self):
100129 env = config ["settings" ]["env" ]
101130 if "BASE_URL" in env :
102131 self .env .set ({"BASE_URL" : env ["BASE_URL" ]})
103- if isinstance (config ["execution" ], dict ):
104- concurrency = config ["execution" ].get ("concurrency" , 1 )
105- iterations = config ["execution" ].get ("iterations" , 1 )
106- else :
107- concurrency = config ["execution" ][0 ].get ("concurrency" , 1 )
108- iterations = config ["execution" ][0 ].get ("iterations" , 1 )
109132
110- if isinstance (concurrency , dict ):
111- concurrency = concurrency .get ("local" , 1 )
112-
113- reporter = "json"
133+ load = self .get_load ()
114134
135+ max_duration = None
136+ concurrency = max (1 , load .concurrency )
137+ if load .duration > 0 :
138+ max_duration = load .duration
139+ if load .iterations > 0 :
140+ repeat_each = concurrency * load .iterations
141+ else :
142+ # playwright support approx 100000 tests (= num tests * repeat_each)
143+ # setting repeat each to some save value. Lets customer setup iterations
144+ # for their test to hold for expected duration....
145+ repeat_each = 1000
146+ else :
147+ iterations = max (1 , load .iterations )
148+ repeat_each = concurrency * iterations
149+
150+ reporter = "@taurus/playwright-custom-reporter"
151+
152+ # self.env.set({"TAURUS_PWREPORT_VERBOSE": "true"})
153+ # TODO: set to false if we will add support for customer reporter
154+ # - keep stdout to customer reporter (or default)
155+ self .env .set ({"TAURUS_PWREPORT_STDOUT" : "true" })
156+ self .env .set ({"TAURUS_PWREPORT_DIR" : self .engine .artifacts_dir })
157+ if max_duration :
158+ self .env .set ({"TAURUS_PWREPORT_DURATION" : str (int (max_duration * 1000 ))})
115159 options = ["--reporter " + reporter ,
116160 "--output " + self .engine .artifacts_dir + "/test-output" ,
117161 "--workers " + str (concurrency ),
118- "--repeat-each " + str (concurrency * iterations )]
162+ "--repeat-each " + str (repeat_each )]
119163
120164 if "browser" in self .get_scenario ().data :
121165 options .append ("--project=" + self .get_scenario ().data ["browser" ])
@@ -136,36 +180,25 @@ def __init__(self, filename, parent_logger):
136180 super (PlaywrightLogReader , self ).__init__ ()
137181 self .log = parent_logger .getChild (self .__class__ .__name__ )
138182 self .filename = filename
139- self .has_reported = False
183+ self .jsonl_reader = IncrementalLineReader (self .log , filename )
184+
185+ def _safe_ms_to_s (self , t ):
186+ if t :
187+ return t / 1000.0
188+ return t
140189
141190 def _read (self , final_pass = False ):
142- if final_pass :
143- if self .has_reported :
144- yield None
145- self .file = FileReader (filename = self .filename , parent_logger = self .log )
146- self .lines = list (self .file .get_lines (last_pass = final_pass ))
147- content = json .loads ("\n " .join (self .lines ))
148- concurrency = content .get ("config" , {}).get ("metadata" , {}).get ("actualWorkers" )
149-
150- suites = content .get ("suites" , [])
151- for suite in suites :
152- specs = suite .get ("specs" , [])
153- for spec in specs :
154- label = spec .get ("title" , "unknown" )
155- tests = spec .get ("tests" , [])
156- for test in tests :
157- results = test .get ("results" , [])
158- if len (results ) > 0 :
159- start_time = results [0 ].get ("startTime" )
160- timestamp = datetime .strptime (start_time , '%Y-%m-%dT%H:%M:%S.%fZ' ).timestamp ()
161- duration = results [0 ].get ("duration" , 0 ) / 1000
162- errors = None
163- errs = results [0 ].get ("errors" )
164- if len (errs ) > 0 :
165- errors = ", " .join (errs )
166- yield timestamp , label , concurrency , duration , None , duration , duration , errors , None , None
167- self .has_reported = True
168- pass
191+ for line in self .jsonl_reader .read (final_pass ):
192+ content = json .loads (line )
193+ # runDetails: title of test, worker, repetition and browser platform
194+ yield (self ._safe_ms_to_s (content .get ("timestamp" )), content .get ("label" ), content .get ("concurency" ),
195+ self ._safe_ms_to_s (content .get ("duration" )),
196+ self ._safe_ms_to_s (content .get ("connectTime" , None )),
197+ self ._safe_ms_to_s (content .get ("latency" , None )),
198+ 1 - int (content .get ("ok" , True )),
199+ content .get ("error" , None ),
200+ content .get ("runDetails" , None ),
201+ content .get ("byte_count" , 0 ))
169202
170203
171204class MochaTester (JavaScriptExecutor ):
@@ -343,36 +376,6 @@ def check_if_installed(self):
343376 return False
344377
345378
346- class PLAYWRIGHT (RequiredTool ):
347- def __init__ (self , tools_dir , ** kwargs ):
348- super (PLAYWRIGHT , self ).__init__ (installable = True , ** kwargs )
349- self .tools_dir = tools_dir
350-
351- def check_if_installed (self ):
352- # currently there seems to be no reliable way to find out whether all Playwright requirements are installed
353- return False
354-
355- def install (self ):
356- cmd_line = ["npx" , "playwright" , "install" ]
357- self .install_cmd (cmd_line + ["--with-deps" ])
358- self .install_cmd (cmd_line )
359- self .install_cmd (cmd_line + ["chromium" ])
360- self .install_cmd (cmd_line + ["firefox" ])
361- self .install_cmd (cmd_line + ["webkit" ])
362-
363- def install_cmd (self , cmdline ):
364- self .log .debug ("Installing Playwright: %s" , cmdline )
365- try :
366- out , err = self .call (cmdline ,cwd = self .tools_dir )
367- except CALL_PROBLEMS as exc :
368- self .log .warning ("'%s' install failed: %s" , cmdline , exc )
369- return
370- if out :
371- self .log .debug ("%s install stdout: %s" , self .tool_name , out )
372- if err :
373- self .log .warning ("%s install stderr: %s" , self .tool_name , err )
374-
375-
376379class NPMPackage (RequiredTool ):
377380 PACKAGE_NAME = ""
378381
@@ -500,3 +503,41 @@ class TaurusNewmanPlugin(RequiredTool):
500503 def __init__ (self , ** kwargs ):
501504 tool_path = os .path .join (RESOURCES_DIR , "newman-reporter-taurus.js" )
502505 super (TaurusNewmanPlugin , self ).__init__ (tool_path = tool_path , installable = False , ** kwargs )
506+
507+ class PlaywrightCustomReporter (NPMLocalModulePackage ):
508+ PACKAGE_NAME = "@taurus/playwright-custom-reporter@1.0.0"
509+ PACKAGE_LOCAL_PATH = "./playwright-custom-reporter"
510+
511+ def check_if_installed (self ):
512+ # always run install for local module to update to latest version
513+ # npm version resolving for local modules is not reliable
514+ return False
515+
516+ class PLAYWRIGHT (RequiredTool ):
517+ def __init__ (self , tools_dir , ** kwargs ):
518+ super (PLAYWRIGHT , self ).__init__ (installable = True , ** kwargs )
519+ self .tools_dir = tools_dir
520+
521+ def check_if_installed (self ):
522+ # currently there seems to be no reliable way to find out whether all Playwright requirements are installed
523+ return False
524+
525+ def install (self ):
526+ cmd_line = ["npx" , "playwright" , "install" ]
527+ self .install_cmd (cmd_line + ["--with-deps" ])
528+ self .install_cmd (cmd_line )
529+ self .install_cmd (cmd_line + ["chromium" ])
530+ self .install_cmd (cmd_line + ["firefox" ])
531+ self .install_cmd (cmd_line + ["webkit" ])
532+
533+ def install_cmd (self , cmdline ):
534+ self .log .debug ("Installing Playwright: %s" , cmdline )
535+ try :
536+ out , err = self .call (cmdline ,cwd = self .tools_dir )
537+ except CALL_PROBLEMS as exc :
538+ self .log .warning ("'%s' install failed: %s" , cmdline , exc )
539+ return
540+ if out :
541+ self .log .debug ("%s install stdout: %s" , self .tool_name , out )
542+ if err :
543+ self .log .warning ("%s install stderr: %s" , self .tool_name , err )
0 commit comments