import asyncio import json import os import re import threading import time from functools import partial from os import path from percy import percy_snapshot from selenium.webdriver import Chrome from selenium.webdriver.support.ui import Select from selenium.webdriver.common.by import By from pywebio.input import * from pywebio.output import * from pywebio.session import * here_dir = path.dirname(path.abspath(__file__)) def get_visible_form(browser): forms = browser.find_elements(By.CSS_SELECTOR, '#input-cards > div') for f in forms: if f.is_displayed(): return f def basic_output(): set_env(title="PyWebIO Test") set_scope('top') put_markdown('### Basic') for i in range(3): put_text('text_%s' % i) put_text('测试空格:20空格:[%s]结束' % (' ' * 20)) for i in range(3): put_text('inline_text_%s' % i, inline=True) put_markdown("""### put_markdown 测试 `行内代码` 无序列表: - 北京 - 上海 - 天津 有序列表: 1. 北京 2. 上海 3. 天津 [链接](./#) ~~删除线~~ """) put_link('链接', '#') put_text('
:') put_html("
") put_markdown('### Style') put_text('Red').style('color:red') put_text('Red').style('color:red') put_markdown('~~del~~').style('color:red') put_table([ ['A', 'B'], ['C', put_text('Red').style('color:red')], ]) put_collapse('title', [ put_text('text').style('margin-left:20px'), put_markdown('~~del~~').style('margin-left:20px'), ], open=True) put_markdown('### Table') put_table([ ['Name', 'Gender', 'Address'], ['Wang', 'M', 'China'], ['Liu', 'W', 'America'], ]) put_table([ ['Wang', 'M', 'China'], ['Liu', 'W', 'America'], ], header=['Name', 'Gender', 'Address']) put_table([ {"Course": "OS", "Score": "80"}, {"Course": "DB", "Score": "93"}, ], header=["Course", "Score"]) put_table([ {"Course": "OS", "Score": "80"}, {"Course": "DB", "Score": "93"}, ], header=[("课程", "Course"), ("得分", "Score")]) img_data = open(path.join(here_dir, 'assets', 'img.png'), 'rb').read() put_table([ ['Type', 'Content'], ['text', '
'], ['html', put_html('X2')], ['buttons', put_buttons(['A', 'B'], onclick=put_text, small=True)], ['markdown', put_markdown('`awesome PyWebIO!`\n - 1\n - 2\n - 3')], ['file', put_file('hello.text', b'')], ['image', put_image(img_data)], ['table', put_table([ ['A', 'B'], [put_markdown('`C`'), put_markdown('`D`')] ])] ]) put_markdown('### Code') with use_scope('scroll_basis'): put_code(json.dumps(dict(name='pywebio', author='wangweimin'), indent=4), 'json') put_text('move ⬆ code block to screen ... :') with use_scope('scroll_basis_btns'): put_buttons(buttons=[ ('BOTTOM', Position.BOTTOM), ('TOP', Position.TOP), ('MIDDLE', Position.MIDDLE), ], onclick=lambda pos: scroll_to('scroll_basis', pos)) def show_popup(): popup('Popup title', [ put_html('

Popup Content

'), 'html:
', put_table([ ['Type', 'Content'], ['html', put_html('X2')], ['text', '
'], ['buttons', put_buttons(['A', 'B'], onclick=put_text)], ['markdown', put_markdown('`Awesome PyWebIO!`')], ['file', put_file('hello.text', b'')], ['table', put_table([['A', 'B'], ['C', 'D']])] ]), put_buttons(['close_popup()'], onclick=lambda _: close_popup()) ], size=PopupSize.NORMAL) with use_scope('popup_btn'): put_buttons([('popup()', '')], onclick=[show_popup]) def edit_row(choice, row): put_text("You click %s button at row %s" % (choice, row), scope='table_cell_buttons') with use_scope('table_cell_buttons'): put_table([ ['Idx', 'Actions'], ['1', put_buttons(['edit', 'delete'], onclick=partial(edit_row, row=1))], ['2', put_buttons(['edit', 'delete'], onclick=partial(edit_row, row=2))], ['3', put_buttons(['edit', 'delete'], onclick=partial(edit_row, row=3))], ]) with use_scope('put_buttons'): put_buttons(['A', 'B', 'C'], onclick=partial(put_text, scope='put_buttons')) # put_button put_button("click me", onclick=lambda: toast("Clicked"), color='success', outline=True) put_markdown('### Image') put_image(img_data) from PIL.Image import open as pil_open pil_img = pil_open(path.join(here_dir, 'assets', 'img.png')) put_image(pil_img, width="30px") put_image('https://cdn.jsdelivr.net/gh/wang0618/pywebio/test/assets/img.png', height="50px") put_file('hello_word.txt', b'hello word!') put_collapse('Collapse', [ 'text', put_markdown('~~删除线~~'), put_table([ ['商品', '价格'], ['苹果', '5.5'], ['香蕉', '7'], ]) ], open=True) put_collapse('title', 'something', open=True) put_scrollable('scrollable\n' * 20, height=50, keep_bottom=True) put_markdown('### Scope') with use_scope('scope1'): put_text("to be cleared") clear() put_text('A') # 输出内容: A put_text('B', position=0) # 输出内容: B A put_text('C', position=-2) # 输出内容: B C A with use_scope('scope2'): put_text('scope2') put_text('scope2') put_text('D', position=1) # 输出内容: B D C A put_text('before=top again', scope='top') with use_scope('to_remove'): put_text('to remove') remove('to_remove') put_markdown('### Info') from pywebio import session session_info = session.info from django.http import HttpRequest from flask import Request from tornado.httputil import HTTPServerRequest from aiohttp.web import BaseRequest from starlette.websockets import WebSocket request_type = { 'tornado': HTTPServerRequest, 'flask': Request, 'django': HttpRequest, 'aiohttp': BaseRequest, 'starlette': WebSocket, } request_ok = isinstance(session_info.request, request_type.get(session_info.backend)) if not request_ok: print('Error: request check error: backend %s, request type %s, class %s' % (session_info.backend, type(session_info.request).__name__, session_info.request)) put_markdown(rf"""### 会话信息 ``` * `user_agent`: * `is_mobile` (bool): {session_info.user_agent.is_mobile} * `is_tablet` (bool): {session_info.user_agent.is_tablet} * `is_pc` (bool): {session_info.user_agent.is_pc} * `is_touch_capable` (bool): {session_info.user_agent.is_touch_capable} * `browser.family` (str): {session_info.user_agent.browser.family} * `os.family` (str): {session_info.user_agent.os.family} * `os.version` (tuple): {session_info.user_agent.os.version} * `os.version_string` (str): {session_info.user_agent.os.version_string} * `device.family` (str): {session_info.user_agent.device.family} * `device.brand` (str): {session_info.user_agent.device.brand} * `device.model` (str): {session_info.user_agent.device.model} * `user_language` (str): {session_info.user_language} * `server_host` (str): {session_info.server_host} * `origin` (str): {session_info.origin or 'http://' + session_info.server_host} * `user_ip` (str): {session_info.user_ip} * `request type check` (str): {request_ok} ``` """) put_markdown('### Layout') put_row([ put_column([ put_code('A'), put_row([ put_code('B1'), None, put_code('B2'), None, put_code('B3'), ]), put_code('C'), ]), None, put_code('python'), None, put_code('python\n' * 20).style('max-height:200px;'), ]) put_grid([ [put_code('[%s,%s]' % (x, y)).style('margin-right:10px;') for y in range(4)] for x in range(5) ], direction='column') put_row([put_code(i).style('margin-right:10px;') for i in range(4)], 'repeat(auto-fill, 25%)') put_markdown('### Span') cell = lambda text: put_code(text).style('margin-right:10px;') put_grid([ [span(cell('A'), col=2), None], [span(cell('C'), row=2, col=2), span(cell('D'), row=2)], [], ], cell_width='1fr', cell_height='1fr') put_table([ ['C'], [span('E', col=2)], ], header=[span('A', row=2), 'B']) put_processbar('processbar', 0.3) set_processbar('processbar', 0.6) put_loading() # output hobby = output(put_text('Coding')) put_table([ ['Name', 'Hobbies'], ['Wang', hobby] ]) hobby.reset(put_text('Movie')) hobby.append(put_text('Music'), put_text('Drama')) hobby.insert(0, put_markdown('**Coding**')) put_table([ ['Name', 'Hobbies'], ['Tom', put_scope('hobby', content=put_text('Coding'))] ]) with use_scope('hobby', clear=True): put_text('Movie') # hobby is reset to Movie with use_scope('hobby'): put_text('Music') put_text('Drama') put_markdown('**Coding**', scope='hobby', position=0) def background_output(): put_text("Background output") set_scope('background') def background(): for i in range(20): put_text('%s ' % i, inline=True, scope='background') t = threading.Thread(target=background) register_thread(t) t.start() async def coro_background_output(): put_text("Background output") set_scope('background') async def background(): for i in range(20): put_text('%s ' % i, inline=True, scope='background') return run_async(background()) def test_output(browser: Chrome, enable_percy=False, action_delay=0.5): """测试输出:: run template.basic_output() template.background_output() # 或者 await template.coro_background_output() hold() """ time.sleep(action_delay * 2) # 等待输出完毕 # get focus browser.find_element(By.TAG_NAME, 'body').click() time.sleep(action_delay * 2) browser.execute_script('$("html, body").scrollTop( $(document).height()+100);') time.sleep(action_delay) if enable_percy: percy_snapshot(browser, name='begin output') tab_btns = browser.find_elements(By.CSS_SELECTOR, '#pywebio-scope-table_cell_buttons button') for btn in tab_btns: time.sleep(action_delay) browser.execute_script("arguments[0].click();", btn) btns = browser.find_elements(By.CSS_SELECTOR, '#pywebio-scope-put_buttons button') for btn in btns: time.sleep(action_delay) browser.execute_script("arguments[0].click();", btn) # 滚动窗口 btns = browser.find_elements(By.CSS_SELECTOR, '#pywebio-scope-scroll_basis_btns button') for btn in btns: time.sleep(action_delay * 2) browser.execute_script("arguments[0].click();", btn) time.sleep(action_delay * 2) browser.execute_script('$("html, body").scrollTop( $(document).height()+100);') time.sleep(action_delay) if enable_percy: percy_snapshot(browser, name='basic output') # popup btn = browser.find_element(By.CSS_SELECTOR, '#pywebio-scope-popup_btn button') browser.execute_script("arguments[0].click();", btn) time.sleep(action_delay * 2) if enable_percy: percy_snapshot(browser, name='popup') browser.execute_script("$('.modal').modal('hide');") def basic_input(): js_res = yield eval_js('''(function(){ for(var i=0;i<=limit;i++) a += i; return a; })()''', a=0, limit=100) assert js_res == 5050 age = yield input("How old are you?", type=NUMBER) put_markdown(f'`{repr(age)}`') password = yield input("Input password", type=PASSWORD) put_markdown(f'`{repr(password)}`') # 下拉选择框 gift = yield select('Which gift you want?', ['keyboard', 'ipad']) put_markdown(f'`{repr(gift)}`') # CheckBox agree = yield checkbox("用户协议", options=['I agree to terms and conditions']) put_markdown(f'`{repr(agree)}`') # Text Area text = yield textarea('Text Area', rows=3, placeholder='Some text') put_markdown(f'`{repr(text)}`') # 文件上传 img = yield file_upload("Select a image:", accept="image/*", max_size=10 ** 7) put_image(img['content'], title=img['filename']) # 输入参数 res = yield input('This is label', type=TEXT, placeholder='This is placeholder,required=True', help_text='This is help text', required=True) put_markdown(f'`{repr(res)}`') # 取消表单 res = yield input_group('cancel test', [input(name='cancel')], cancelable=True) put_markdown(f'`{repr(res)}`') # 校验函数 def check_age(p): # 检验函数校验通过时返回None,否则返回错误消息 if p < 10: return 'Too young!!' if p > 60: return 'Too old!!' age = yield input("How old are you?", type=NUMBER, validate=check_age, help_text='age in [10, 60]') put_markdown(f'`{repr(age)}`') # Codemirror code = yield textarea('Code Edit', code={ 'mode': "python", # 编辑区代码语言 'theme': 'darcula', # 编辑区darcula主题, Visit https://codemirror.net/demo/theme.html#cobalt to get more themes }, value='import something\n# Write your python code') put_markdown(f'`{repr(code)}`') # 输入组 cancelable info = yield input_group("Cancelable", [ input('Input your name', name='name'), input('Input your age', name='age', type=NUMBER, validate=check_age, help_text='age in [10, 60]') ], cancelable=True) put_markdown(f'`{repr(info)}`') # input action def set_now_ts(set_value): set_value('set from action') val = yield input('Input action', action=('Set value', set_now_ts)) assert val == 'set from action' def check_form(data): # 检验函数校验通过时返回None,否则返回 (input name,错误消息) if len(data['password']) > 6: return ('password', 'password太长!') check_item_data = [] def check_item(data): check_item_data.append(repr(data)) info = yield input_group('Input group', [ input('Text', type=TEXT, datalist=['data-%s' % i for i in range(10)], name='text', required=True, help_text='required=True', validate=check_item), input('Number', type=NUMBER, value="42", name='number', validate=check_item), input('Float', type=FLOAT, name='float', validate=check_item), input('Password', type=PASSWORD, name='password', validate=check_item), textarea('Textarea', rows=3, maxlength=20, name='textarea', help_text='rows=3, maxlength=20', validate=check_item), textarea('Code', name='code', code={ 'lineNumbers': False, 'indentUnit': 2, }, value='import something\n# Write your python code', validate=check_item), select('select-multiple', [ {'label': '标签0,selected', 'value': '0', 'selected': True}, {'label': '标签1,disabled', 'value': '1', 'disabled': True}, ('标签2,selected', '2', True), ('标签3', '3'), ('标签4,disabled', '4', False, True), '标签5,selected', ], name='select-multiple', multiple=True, value=['标签5,selected'], required=True, help_text='required至少选择一项', validate=check_item), select('select', [ {'label': '标签0', 'value': '0', 'selected': False}, {'label': '标签1,disabled', 'value': '1', 'disabled': True}, ('标签2', '2', False), ('标签3', '3'), ('标签4,disabled', '4', False, True), '标签5,selected', ], name='select', value=['标签5,selected'], validate=check_item), checkbox('checkbox-inline', [ {'label': '标签0,selected', 'value': '0', 'selected': False}, {'label': '标签1,disabled', 'value': '1', 'disabled': True}, ('标签2,selected', '2', True), ('标签3', '3'), ('标签4,disabled', '4', False, True), '标签5,selected', ], inline=True, name='checkbox-inline', value=['标签5,selected', '标签0', '标签0,selected'], validate=check_item), checkbox('checkbox', [ {'label': '标签0,selected', 'value': '0', 'selected': True}, {'label': '标签1,disabled', 'value': '1', 'disabled': True}, ('标签2,selected', '2', True), ('标签3', '3'), ('标签4,disabled', '4', False, True), '标签5', ], name='checkbox', validate=check_item), radio('radio-inline', [ {'label': '标签0', 'value': '0', 'selected': False}, {'label': '标签1,disabled', 'value': '1', 'disabled': True}, ('标签2', '2', False), ('标签3', '3'), ('标签4,disabled', '4', False, True), '标签5,selected', ], inline=True, name='radio-inline', value='标签5,selected', validate=check_item), radio('radio', [ {'label': '标签0', 'value': '0', 'selected': False}, {'label': '标签1,disabled', 'value': '1', 'disabled': True}, ('标签2', '2', False), ('标签3', '3'), ('标签4,disabled', '4', False, True), '标签5,selected', ], inline=False, name='radio', value='标签5,selected', validate=check_item), file_upload('file_upload', name='file_upload', max_size='10m'), actions('actions', [ {'label': '提交', 'value': 'submit'}, ('提交2', 'submit2'), '提交3', {'label': 'disabled', 'disabled': True}, ('重置', 'reset', 'reset'), {'label': '取消', 'type': 'cancel'}, ], name='actions', help_text='actions'), ], validate=check_form) put_text('`validate()` log:') put_code(json.dumps(sorted(list(set(check_item_data))), indent=4, ensure_ascii=False), 'json') put_text('Form result:') if info: put_code(json.dumps([repr(i) for i in sorted(info.items())], indent=4, ensure_ascii=False), 'json') # yield actions(['Continue']) def background_input(): def background(): time.sleep(1) res = input('background') put_markdown(f'`background: {repr(res)}`') t = threading.Thread(target=background) register_thread(t) t.start() res = input('front') put_markdown(f'`front: {repr(res)}`') async def coro_background_input(): async def background(): await asyncio.sleep(1) res = await input('background') put_markdown(f'`background: {repr(res)}`') run_async(background()) res = await input('front') put_markdown(f'`front: {repr(res)}`') async def flask_coro_background_input(): async def background(): await run_asyncio_coroutine(asyncio.sleep(1)) res = await input('background') put_markdown(f'`background: {repr(res)}`') run_async(background()) res = await input('front') put_markdown(f'`front: {repr(res)}`') def test_input(browser: Chrome, enable_percy=False, action_delay=0.5): """测试输入:: run template.basic_input() actions(['Continue']) template.background_input() # 或者 await template.coro_background_input() / flask_coro_background_input """ browser.find_element(By.CSS_SELECTOR, '#input-container input').send_keys("22") browser.find_element(By.TAG_NAME, 'form').submit() time.sleep(action_delay) browser.find_element(By.CSS_SELECTOR, '#input-container input').send_keys("secret") browser.find_element(By.TAG_NAME, 'form').submit() time.sleep(action_delay) browser.find_element(By.TAG_NAME, 'form').submit() # checkbox time.sleep(action_delay) browser.execute_script("arguments[0].click();", browser.find_element(By.CSS_SELECTOR, '#input-container input')) browser.find_element(By.TAG_NAME, 'form').submit() # Text Area time.sleep(action_delay) browser.find_element(By.CSS_SELECTOR, '#input-container textarea').send_keys(" ".join(str(i) for i in range(20))) browser.find_element(By.TAG_NAME, 'form').submit() # file time.sleep(action_delay) img_path = path.join(here_dir, 'assets', 'img.png') browser.find_element(By.CSS_SELECTOR, '#input-container input').send_keys(img_path) browser.find_element(By.TAG_NAME, 'form').submit() # text time.sleep(action_delay) browser.find_element(By.CSS_SELECTOR, '#input-container input').send_keys("text") browser.find_element(By.TAG_NAME, 'form').submit() # 表单取消 time.sleep(action_delay) browser.execute_script("arguments[0].click();", browser.find_element(By.CSS_SELECTOR, '.pywebio_cancel_btn')) # valid func, age in [10, 60] time.sleep(action_delay) browser.find_element(By.CSS_SELECTOR, '#input-container input').send_keys("1") browser.find_element(By.TAG_NAME, 'form').submit() time.sleep(action_delay) browser.find_element(By.CSS_SELECTOR, '#input-container input').clear() browser.find_element(By.CSS_SELECTOR, '#input-container input').send_keys("90") browser.find_element(By.TAG_NAME, 'form').submit() time.sleep(action_delay) browser.find_element(By.CSS_SELECTOR, '#input-container input').clear() browser.find_element(By.CSS_SELECTOR, '#input-container input').send_keys("23") browser.find_element(By.TAG_NAME, 'form').submit() # code time.sleep(action_delay) # browser.find_element(By.CSS_SELECTOR, 'textarea').send_keys(" ".join(str(i) for i in range(20))) browser.find_element(By.TAG_NAME, 'form').submit() # Cancelable from group time.sleep(action_delay) browser.find_element(By.NAME, 'name').send_keys("name") time.sleep(action_delay * 2) browser.find_element(By.NAME, 'age').send_keys("90") browser.find_element(By.TAG_NAME, 'form').submit() browser.execute_script('$("html, body").scrollTop( $(document).height()+100);') time.sleep(action_delay) if enable_percy: percy_snapshot(browser, name='input group invalid') time.sleep(action_delay) browser.find_element(By.NAME, 'age').clear() browser.find_element(By.NAME, 'age').send_keys("23") browser.find_element(By.TAG_NAME, 'form').submit() # callback actions time.sleep(action_delay) browser.execute_script("arguments[0].click();", browser.find_element(By.CSS_SELECTOR, 'form button[type="button"]')) time.sleep(action_delay) # input action time.sleep(action_delay) browser.execute_script("arguments[0].click();", browser.find_element(By.CSS_SELECTOR, 'form button[type="button"]')) time.sleep(action_delay) browser.find_element(By.TAG_NAME, 'form').submit() # Input group time.sleep(action_delay) browser.execute_script('$("html, body").scrollTop( $(document).height()+100);') time.sleep(action_delay) if enable_percy: percy_snapshot(browser, name='input group all') browser.find_element(By.NAME, 'text').send_keys("name") browser.find_element(By.NAME, 'number').send_keys("20") browser.find_element(By.NAME, 'float').send_keys("3.1415") browser.find_element(By.NAME, 'password').send_keys("password") browser.find_element(By.NAME, 'textarea').send_keys(" ".join(str(i) for i in range(20))) # browser.find_element(By.CSS_SELECTOR, '[name="code"]').send_keys(" ".join(str(i) for i in range(10))) Select(browser.find_element(By.NAME, 'select-multiple')).select_by_index(0) # browser. find_element_by_css_selector('[name="select"]'). send_keys("name") # browser. find_element_by_css_selector('[name="checkbox-inline"]'). send_keys("name") # browser. find_element_by_css_selector('[name="checkbox"]'). send_keys("name") # browser. find_element_by_css_selector('[name="radio-inline"]'). send_keys("name") # browser. find_element_by_css_selector('[name="radio"]'). send_keys("name") browser.find_element(By.NAME, 'file_upload').send_keys(path.join(here_dir, 'assets', 'helloworld.txt')) browser.execute_script("$('form button').eq(1).click()") time.sleep(action_delay * 2) browser.execute_script('$("html, body").scrollTop( $(document).height()+100);') time.sleep(action_delay) if enable_percy: percy_snapshot(browser, name='input group all invalid') browser.find_element(By.NAME, 'password').clear() browser.find_element(By.NAME, 'password').send_keys("123") browser.execute_script("$('form button[type=\"submit\"]').eq(1).click()") time.sleep(action_delay * 2) browser.execute_script('$("html, body").scrollTop( $(document).height()+100);') time.sleep(action_delay * 2) if enable_percy: percy_snapshot(browser, name='input group all submit') browser.find_element(By.CSS_SELECTOR, 'form').submit() # background time.sleep(action_delay * 6) get_visible_form(browser).find_element(By.CSS_SELECTOR, '#input-container input').send_keys("background") get_visible_form(browser).find_element(By.TAG_NAME, 'form').submit() # front time.sleep(action_delay * 2) get_visible_form(browser).find_element(By.CSS_SELECTOR, '#input-container input').send_keys("front") get_visible_form(browser).find_element(By.TAG_NAME, 'form').submit() def set_defer_call(): def deferred_1(): open('test_defer.tmp', 'w').write('deferred_1') def deferred_2(): open('test_defer.tmp', 'a').write('deferred_2') defer_call(deferred_1) defer_call(deferred_2) def test_defer_call(): output = open('test_defer.tmp').read() assert "deferred_1" in output assert "deferred_2" in output os.remove('test_defer.tmp') def save_output(browser: Chrome, filename=None, process_func=None): """获取输出区html源码,并去除随机元素,供之后diff比较 :param browser: :param filename: 保存文件名, 为 None 时,不保存为文件 :param process_func: 自定义数据处理函数 :return: 处理前后的html文本 """ raw_html = browser.find_element(By.ID, 'markdown-body').get_attribute('innerHTML') html = re.sub(r'"pywebio-scope-.*?"', '', raw_html) html = re.sub(r'id="pywebio-.*?"', '', html) html = re.sub(r"\('pywebio-.*?'\)", '', html) html = re.sub(r"WebIO.pushData\(.*?\)", '', html) html = re.sub(r"", r'>\n', html) # 进行断行方便后续的diff判断 html = html.replace('"opacity: 1;"', '""').replace(' open=""', '') # so wired html = html.strip() if process_func: html = process_func(html) if filename: open(path.join(here_dir, 'output', filename), 'w').write(html) return raw_html, html