"""Capture all keyboard layouts and check clipping, round bounds and dark edge gaps.""" import argparse,datetime,importlib.util,json,math,pathlib,time from PIL import Image import layout APP=pathlib.Path(__file__).resolve().parents[1] spec=importlib.util.spec_from_file_location('native',APP/'tests/native.py') native=importlib.util.module_from_spec(spec);spec.loader.exec_module(native);emu=native.emu def dark_edges(path,profile): im=Image.open(path).convert('RGB');points=[] if profile.startswith('round'): # 466px screen inside the vendor's 624px hardware skin. Stay 7px inside its edge. im=im.crop((70,79,536,545)) for y in range(466): for x in range(466): radius=math.hypot(x-233,y-233) if 224<=radius<=226 and max(im.getpixel((x,y)))<80:points.append([x,y]) else: for y in range(4,446): for x in [3,386]: if max(im.getpixel((x,y)))<80:points.append([x,y]) for x in range(4,386): for y in [3,446]: if max(im.getpixel((x,y)))<80:points.append([x,y]) return len(points) def main(): parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--work',type=pathlib.Path,required=True);parser.add_argument('--output',type=pathlib.Path,required=True) parser.add_argument('--profiles',nargs='+',default=emu.PROFILES);args=parser.parse_args();args.output.mkdir(parents=True,exist_ok=True) report={'recorded_at':datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8))).isoformat(),'artifacts':{},'screens':[]} def capture(profile,name): # Inspector history can associate the new widget tree with the preceding # back buffer on an otherwise idle page. Request two real redraws, restore # the original caption, then retain only the fresh native frame. emu.evaluate("var view=require('@system.router').default.getPages().slice(-1)[0];var captionKey=typeof view.hint==='string'?'hint':'status';var oldCaption=view[captionKey];view[captionKey]=oldCaption+' '") time.sleep(.2) emu.evaluate("view[captionKey]=oldCaption") time.sleep(.4);emu.snapshot(profile+'-'+name,args.output) time.sleep(.3);emu.snapshot(profile+'-'+name,args.output) result=layout.inspect(args.output/(profile+'-'+name+'.tree.json'),profile) result['dark_edge_pixels']=dark_edges(args.output/(profile+'-'+name+'.png'),profile) result['passed']=result['passed'] and result['dark_edge_pixels']==0 result.update(profile=profile,screen=name);report['screens'].append(result) native.save(args.output/'visual-results.json',report) print(json.dumps(result,ensure_ascii=False),flush=True) if not result['passed']:raise RuntimeError('Visual regression: '+profile+'-'+name) for profile in args.profiles: process,artifacts=emu.start(profile,args.work,'inputmethod');report['artifacts'][profile]=artifacts try: capture(profile,'home') emu.evaluate("var r=require('@system.router').default,t=require('@system.test').default;var root=r.getPage(0);t.getByClass('document').click()") time.sleep(1);emu.evaluate("var p=require('pkg://com.sydxky.rtos.inputmethod/common/ime-lifecycle.js').activeInputs().filter(input=>input.$page===r.getPage(1))[0];p.composition='64426';p.schedule()") time.sleep(.5);capture(profile,'t9') emu.evaluate("p.composition='shi';p.mode='py';p.renderKeys();p.schedule()") time.sleep(.5);capture(profile,'pinyin') emu.evaluate("p.expand()");capture(profile,'candidates') emu.evaluate("p.closePanel();p.composition='nihao';p.schedule()") time.sleep(.5);emu.evaluate("p.expand()");capture(profile,'phrase-candidates') emu.evaluate("p.showTools()");capture(profile,'tools') emu.evaluate("p.composition='';p.setMode('14');p.composition='c3754';p.schedule()") time.sleep(.5);capture(profile,'14-key') emu.evaluate("p.expand()");capture(profile,'14-candidates') for mode in ['en','num','sym']: emu.evaluate("p.composition='';p.setMode("+json.dumps(mode)+")") capture(profile,mode) emu.evaluate("p.symbols()");capture(profile,'sym2') finally: if process.poll()is None:process.terminate() process.wait(timeout=10);process.stdin.close() report['passed']=all(screen['passed']for screen in report['screens']);native.save(args.output/'visual-results.json',report) if __name__=='__main__':main()