Coverage for selenible/cli.py: 66%

150 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-05 14:24 +0000

1import inspect 

2import json 

3import os 

4import pprint 

5import sys 

6from logging import ( 

7 DEBUG, 

8 INFO, 

9 WARNING, 

10 FileHandler, 

11 Formatter, 

12 StreamHandler, 

13 captureWarnings, 

14 getLogger, 

15) 

16 

17import click 

18import jsonschema 

19import yaml 

20 

21from .drivers import ( 

22 Android, 

23 Base, 

24 Chrome, 

25 Dummy, 

26 Edge, 

27 Firefox, 

28 Ie, 

29 Opera, 

30 Phantom, 

31 Remote, 

32 Safari, 

33 WebKitGTK, 

34) 

35from .version import VERSION 

36 

37drvmap = { 

38 "phantom": Phantom, 

39 "phantomjs": Phantom, 

40 "chrome": Chrome, 

41 "firefox": Firefox, 

42 "safari": Safari, 

43 "edge": Edge, 

44 "webkit": WebKitGTK, 

45 "dummy": Dummy, 

46 "ie": Ie, 

47 "opera": Opera, 

48 "android": Android, 

49 "remote": Remote, 

50} 

51 

52 

53@click.group(invoke_without_command=True) 

54@click.pass_context 

55@click.version_option(version=VERSION, prog_name="selenible") 

56@click.option("--verbose", is_flag=True) 

57@click.option("--quiet", is_flag=True) 

58@click.option("--logfile", type=click.Path()) 

59def cli(ctx, verbose, quiet, logfile): 

60 logfmt = "%(asctime)s %(levelname)s %(name)s %(message)s" 

61 fmt = Formatter(fmt=logfmt) 

62 lg = getLogger() 

63 if verbose: 63 ↛ 64line 63 didn't jump to line 64 because the condition on line 63 was never true

64 lg.setLevel(DEBUG) 

65 elif quiet: 

66 lg.setLevel(WARNING) 

67 else: 

68 lg.setLevel(INFO) 

69 if logfile is not None: 69 ↛ 70line 69 didn't jump to line 70 because the condition on line 69 was never true

70 newhdl = FileHandler(logfile) 

71 newhdl.setFormatter(fmt) 

72 lg.addHandler(newhdl) 

73 else: 

74 newhdl = StreamHandler() 

75 newhdl.setFormatter(fmt) 

76 lg.addHandler(newhdl) 

77 if ctx.invoked_subcommand is None: 77 ↛ 78line 77 didn't jump to line 78 because the condition on line 77 was never true

78 print(ctx.get_help()) 

79 

80 

81def loadmodules(driver, extension): 

82 def_modules = ["ctrl", "browser", "content", "imageproc"] 

83 for i in def_modules: 

84 Base.load_modules(i) 

85 for ext in extension: 

86 Base.load_modules(ext) 

87 drvcls = drvmap.get(driver, Phantom) 

88 drvcls.load_modules(drvcls.__name__.lower()) 

89 for ext in extension: 

90 drvcls.load_modules(ext) 

91 return drvcls 

92 

93 

94@cli.command(help="run playbook") 

95@click.option("--driver", default="phantom", type=click.Choice(drvmap.keys())) 

96@click.option("--extension", "-x", multiple=True) 

97@click.option("--step", is_flag=True, default=False) 

98@click.option("--screenshot", is_flag=True, default=False) 

99@click.option("-e", multiple=True) 

100@click.option("--var", type=click.File("r"), required=False) 

101@click.argument("input", type=click.File("r"), required=False) 

102def run(input, driver, step, screenshot, var, e, extension): 

103 captureWarnings(True) 

104 drvcls = loadmodules(driver, extension) 

105 if input is not None: 

106 prog = yaml.safe_load(input) 

107 b = drvcls() 

108 b.variables["driver"] = driver 

109 for k, v in os.environ.items(): 

110 b.variables[k] = v 

111 if var is not None: 

112 b.load_vars(var) 

113 for x in e: 

114 if x.find("=") == -1: 

115 b.variables[k] = True 

116 else: 

117 k, v = x.split("=", 1) 

118 try: 

119 b.variables[k] = json.loads(v) 

120 except Exception: # noqa: BLE001 -- any parse failure means "treat as plain string" 

121 b.variables[k] = v 

122 b.step = step 

123 b.save_every = screenshot 

124 b.run(prog) 

125 else: 

126 click.echo("show usage: --help") 

127 

128 

129@cli.command("list-modules", help="list modules") 

130@click.option("--driver", default="phantom", type=click.Choice(drvmap.keys())) 

131@click.option("--extension", "-x", multiple=True) 

132@click.option("--pattern", default=None) 

133def list_modules(driver, extension, pattern): 

134 drvcls = loadmodules(driver, extension) 

135 from texttable import Texttable 

136 

137 table = Texttable() 

138 table.set_cols_align(["l", "l"]) 

139 # table.set_deco(Texttable.HEADER) 

140 table.header(["Module", "Description"]) 

141 mods = drvcls.listmodule() 

142 for k in sorted(mods.keys()): 

143 if pattern is not None and k.find(pattern) == -1: 

144 continue 

145 table.add_row([k, mods[k]]) 

146 print(table.draw()) 

147 

148 

149@cli.command("dump-schema", help="dump json schema") 

150@click.option("--driver", default="phantom", type=click.Choice(drvmap.keys())) 

151@click.option("--extension", "-x", multiple=True) 

152@click.option( 

153 "--format", default="yaml", type=click.Choice(["yaml", "json", "python", "pprint"]) 

154) 

155def dump_schema(driver, extension, format): 

156 drvcls = loadmodules(driver, extension) 

157 if format == "yaml": 

158 yaml.dump(drvcls.schema, sys.stdout, default_flow_style=False) 

159 elif format == "json": 

160 json.dump(drvcls.schema, fp=sys.stdout, ensure_ascii=False) 

161 elif format == "python": 

162 print(drvcls.schema) 

163 elif format == "pprint": 163 ↛ 166line 163 didn't jump to line 166 because the condition on line 163 was always true

164 pprint.pprint(drvcls.schema) 

165 else: 

166 raise Exception(f"unknown format: {format}") 

167 

168 

169@cli.command(help="validate by json schema") 

170@click.option("--driver", default="phantom", type=click.Choice(drvmap.keys())) 

171@click.option("--extension", "-x", multiple=True) 

172@click.argument("input", type=click.File("r"), required=False) 

173def validate(driver, extension, input): 

174 drvcls = loadmodules(driver, extension) 

175 prog = yaml.safe_load(input) 

176 try: 

177 click.echo("validating...", nl=False) 

178 jsonschema.validate(prog, drvcls.schema) 

179 click.echo("OK") 

180 sys.exit(0) 

181 except jsonschema.exceptions.ValidationError as e: 

182 click.echo("failed") 

183 click.echo(e) 

184 sys.exit(1) 

185 

186 

187@cli.command("browser-options", help="show browser options") 

188@click.option("--driver", default="phantom", type=click.Choice(drvmap.keys())) 

189@click.option("--mode", default="example", type=click.Choice(["example", "doc"])) 

190def browser_options(driver, mode): 

191 drvcls = loadmodules(driver, []) 

192 drv = drvcls() 

193 if mode == "doc": 

194 print(inspect.getdoc(drv.driver.__init__)) 

195 return 

196 opts = drv.get_options() 

197 sig = inspect.signature(drv.driver.__init__) 

198 res = {} 

199 for k, v in sig.parameters.items(): 

200 res[k] = v.default 

201 if opts != {}: 201 ↛ 202line 201 didn't jump to line 202 because the condition on line 201 was never true

202 res["options"] = {} 

203 for f in dir(opts): 

204 if f.startswith("__") or f.endswith("__"): 

205 continue 

206 if callable(getattr(opts, f)): 

207 s2 = inspect.signature(getattr(opts, f)) 

208 res["options"][f] = [str(x) for x in s2.parameters.values()] 

209 yaml.dump({"browser_setting": res}, sys.stdout, default_flow_style=False) 

210 

211 

212if __name__ == "__main__": 212 ↛ 213line 212 didn't jump to line 213 because the condition on line 212 was never true

213 cli()