Coverage for selenible/cli.py: 66%
150 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-05 14:24 +0000
« 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)
17import click
18import jsonschema
19import yaml
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
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}
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())
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
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")
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
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())
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}")
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)
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)
212if __name__ == "__main__": 212 ↛ 213line 212 didn't jump to line 213 because the condition on line 212 was never true
213 cli()