template.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777
  1. import asyncio
  2. import json
  3. import os
  4. import re
  5. import threading
  6. import time
  7. from functools import partial
  8. from os import path
  9. from percy import percy_snapshot
  10. from selenium.webdriver import Chrome
  11. from selenium.webdriver.support.ui import Select
  12. from selenium.webdriver.common.by import By
  13. from pywebio.input import *
  14. from pywebio.output import *
  15. from pywebio.session import *
  16. here_dir = path.dirname(path.abspath(__file__))
  17. def get_visible_form(browser):
  18. forms = browser.find_elements(By.CSS_SELECTOR, '#input-cards > div')
  19. for f in forms:
  20. if f.is_displayed():
  21. return f
  22. def basic_output():
  23. set_env(title="PyWebIO Test")
  24. set_scope('top')
  25. put_markdown('### Basic')
  26. for i in range(3):
  27. put_text('text_%s' % i)
  28. put_text('测试空格:20空格:[%s]结束' % (' ' * 20))
  29. for i in range(3):
  30. put_text('inline_text_%s' % i, inline=True)
  31. put_markdown("""### put_markdown 测试
  32. `行内代码`
  33. 无序列表:
  34. - 北京
  35. - 上海
  36. - 天津
  37. 有序列表:
  38. 1. 北京
  39. 2. 上海
  40. 3. 天津
  41. [链接](./#)
  42. ~~删除线~~
  43. """)
  44. put_link('链接', '#')
  45. put_text('<hr/>:')
  46. put_html("<hr/>")
  47. put_markdown('### Style')
  48. put_text('Red').style('color:red')
  49. put_text('Red').style('color:red')
  50. put_markdown('~~del~~').style('color:red')
  51. put_table([
  52. ['A', 'B'],
  53. ['C', put_text('Red').style('color:red')],
  54. ])
  55. put_collapse('title', [
  56. put_text('text').style('margin-left:20px'),
  57. put_markdown('~~del~~').style('margin-left:20px'),
  58. ], open=True)
  59. put_markdown('### Table')
  60. put_table([
  61. ['Name', 'Gender', 'Address'],
  62. ['Wang', 'M', 'China'],
  63. ['Liu', 'W', 'America'],
  64. ])
  65. put_table([
  66. ['Wang', 'M', 'China'],
  67. ['Liu', 'W', 'America'],
  68. ], header=['Name', 'Gender', 'Address'])
  69. put_table([
  70. {"Course": "OS", "Score": "80"},
  71. {"Course": "DB", "Score": "93"},
  72. ], header=["Course", "Score"])
  73. put_table([
  74. {"Course": "OS", "Score": "80"},
  75. {"Course": "DB", "Score": "93"},
  76. ], header=[("课程", "Course"), ("得分", "Score")])
  77. img_data = open(path.join(here_dir, 'assets', 'img.png'), 'rb').read()
  78. put_table([
  79. ['Type', 'Content'],
  80. ['text', '<hr/>'],
  81. ['html', put_html('X<sup>2</sup>')],
  82. ['buttons', put_buttons(['A', 'B'], onclick=put_text, small=True)],
  83. ['markdown', put_markdown('`awesome PyWebIO!`\n - 1\n - 2\n - 3')],
  84. ['file', put_file('hello.text', b'')],
  85. ['image', put_image(img_data)],
  86. ['table', put_table([
  87. ['A', 'B'],
  88. [put_markdown('`C`'), put_markdown('`D`')]
  89. ])]
  90. ])
  91. put_markdown('### Code')
  92. with use_scope('scroll_basis'):
  93. put_code(json.dumps(dict(name='pywebio', author='wangweimin'), indent=4), 'json')
  94. put_text('move ⬆ code block to screen ... :')
  95. with use_scope('scroll_basis_btns'):
  96. put_buttons(buttons=[
  97. ('BOTTOM', Position.BOTTOM),
  98. ('TOP', Position.TOP),
  99. ('MIDDLE', Position.MIDDLE),
  100. ], onclick=lambda pos: scroll_to('scroll_basis', pos))
  101. def show_popup():
  102. popup('Popup title', [
  103. put_html('<h3>Popup Content</h3>'),
  104. 'html: <br/>',
  105. put_table([
  106. ['Type', 'Content'],
  107. ['html', put_html('X<sup>2</sup>')],
  108. ['text', '<hr/>'],
  109. ['buttons', put_buttons(['A', 'B'], onclick=put_text)],
  110. ['markdown', put_markdown('`Awesome PyWebIO!`')],
  111. ['file', put_file('hello.text', b'')],
  112. ['table', put_table([['A', 'B'], ['C', 'D']])]
  113. ]),
  114. put_buttons(['close_popup()'], onclick=lambda _: close_popup())
  115. ], size=PopupSize.NORMAL)
  116. with use_scope('popup_btn'):
  117. put_buttons([('popup()', '')], onclick=[show_popup])
  118. def edit_row(choice, row):
  119. put_text("You click %s button at row %s" % (choice, row), scope='table_cell_buttons')
  120. with use_scope('table_cell_buttons'):
  121. put_table([
  122. ['Idx', 'Actions'],
  123. ['1', put_buttons(['edit', 'delete'], onclick=partial(edit_row, row=1))],
  124. ['2', put_buttons(['edit', 'delete'], onclick=partial(edit_row, row=2))],
  125. ['3', put_buttons(['edit', 'delete'], onclick=partial(edit_row, row=3))],
  126. ])
  127. with use_scope('put_buttons'):
  128. put_buttons(['A', 'B', 'C'], onclick=partial(put_text, scope='put_buttons'))
  129. # put_button
  130. put_button("click me", onclick=lambda: toast("Clicked"), color='success', outline=True)
  131. put_markdown('### Image')
  132. put_image(img_data)
  133. from PIL.Image import open as pil_open
  134. pil_img = pil_open(path.join(here_dir, 'assets', 'img.png'))
  135. put_image(pil_img, width="30px")
  136. put_image('https://cdn.jsdelivr.net/gh/wang0618/pywebio/test/assets/img.png', height="50px")
  137. put_file('hello_word.txt', b'hello word!')
  138. put_collapse('Collapse', [
  139. 'text',
  140. put_markdown('~~删除线~~'),
  141. put_table([
  142. ['商品', '价格'],
  143. ['苹果', '5.5'],
  144. ['香蕉', '7'],
  145. ])
  146. ], open=True)
  147. put_collapse('title', 'something', open=True)
  148. put_scrollable('scrollable\n' * 20, height=50, keep_bottom=True)
  149. put_markdown('### Scope')
  150. with use_scope('scope1'):
  151. put_text("to be cleared")
  152. clear()
  153. put_text('A') # 输出内容: A
  154. put_text('B', position=0) # 输出内容: B A
  155. put_text('C', position=-2) # 输出内容: B C A
  156. with use_scope('scope2'):
  157. put_text('scope2')
  158. put_text('scope2')
  159. put_text('D', position=1) # 输出内容: B D C A
  160. put_text('before=top again', scope='top')
  161. with use_scope('to_remove'):
  162. put_text('to remove')
  163. remove('to_remove')
  164. put_markdown('### Info')
  165. from pywebio import session
  166. session_info = session.info
  167. from django.http import HttpRequest
  168. from flask import Request
  169. from tornado.httputil import HTTPServerRequest
  170. from aiohttp.web import BaseRequest
  171. from starlette.websockets import WebSocket
  172. request_type = {
  173. 'tornado': HTTPServerRequest,
  174. 'flask': Request,
  175. 'django': HttpRequest,
  176. 'aiohttp': BaseRequest,
  177. 'starlette': WebSocket,
  178. }
  179. request_ok = isinstance(session_info.request, request_type.get(session_info.backend))
  180. if not request_ok:
  181. print('Error: request check error: backend %s, request type %s, class %s' %
  182. (session_info.backend, type(session_info.request).__name__, session_info.request))
  183. put_markdown(rf"""### 会话信息
  184. ```
  185. * `user_agent`:
  186. * `is_mobile` (bool): {session_info.user_agent.is_mobile}
  187. * `is_tablet` (bool): {session_info.user_agent.is_tablet}
  188. * `is_pc` (bool): {session_info.user_agent.is_pc}
  189. * `is_touch_capable` (bool): {session_info.user_agent.is_touch_capable}
  190. * `browser.family` (str): {session_info.user_agent.browser.family}
  191. * `os.family` (str): {session_info.user_agent.os.family}
  192. * `os.version` (tuple): {session_info.user_agent.os.version}
  193. * `os.version_string` (str): {session_info.user_agent.os.version_string}
  194. * `device.family` (str): {session_info.user_agent.device.family}
  195. * `device.brand` (str): {session_info.user_agent.device.brand}
  196. * `device.model` (str): {session_info.user_agent.device.model}
  197. * `user_language` (str): {session_info.user_language}
  198. * `server_host` (str): {session_info.server_host}
  199. * `origin` (str): {session_info.origin or 'http://' + session_info.server_host}
  200. * `user_ip` (str): {session_info.user_ip}
  201. * `request type check` (str): {request_ok}
  202. ```
  203. """)
  204. put_markdown('### Layout')
  205. put_row([
  206. put_column([
  207. put_code('A'),
  208. put_row([
  209. put_code('B1'), None,
  210. put_code('B2'), None,
  211. put_code('B3'),
  212. ]),
  213. put_code('C'),
  214. ]), None,
  215. put_code('python'), None,
  216. put_code('python\n' * 20).style('max-height:200px;'),
  217. ])
  218. put_grid([
  219. [put_code('[%s,%s]' % (x, y)).style('margin-right:10px;') for y in range(4)]
  220. for x in range(5)
  221. ], direction='column')
  222. put_row([put_code(i).style('margin-right:10px;') for i in range(4)], 'repeat(auto-fill, 25%)')
  223. put_markdown('### Span')
  224. cell = lambda text: put_code(text).style('margin-right:10px;')
  225. put_grid([
  226. [span(cell('A'), col=2), None],
  227. [span(cell('C'), row=2, col=2), span(cell('D'), row=2)],
  228. [],
  229. ], cell_width='1fr', cell_height='1fr')
  230. put_table([
  231. ['C'],
  232. [span('E', col=2)],
  233. ], header=[span('A', row=2), 'B'])
  234. put_processbar('processbar', 0.3)
  235. set_processbar('processbar', 0.6)
  236. put_loading()
  237. # output
  238. hobby = output(put_text('Coding'))
  239. put_table([
  240. ['Name', 'Hobbies'],
  241. ['Wang', hobby]
  242. ])
  243. hobby.reset(put_text('Movie'))
  244. hobby.append(put_text('Music'), put_text('Drama'))
  245. hobby.insert(0, put_markdown('**Coding**'))
  246. put_table([
  247. ['Name', 'Hobbies'],
  248. ['Tom', put_scope('hobby', content=put_text('Coding'))]
  249. ])
  250. with use_scope('hobby', clear=True):
  251. put_text('Movie') # hobby is reset to Movie
  252. with use_scope('hobby'):
  253. put_text('Music')
  254. put_text('Drama')
  255. put_markdown('**Coding**', scope='hobby', position=0)
  256. def background_output():
  257. put_text("Background output")
  258. set_scope('background')
  259. def background():
  260. for i in range(20):
  261. put_text('%s ' % i, inline=True, scope='background')
  262. t = threading.Thread(target=background)
  263. register_thread(t)
  264. t.start()
  265. async def coro_background_output():
  266. put_text("Background output")
  267. set_scope('background')
  268. async def background():
  269. for i in range(20):
  270. put_text('%s ' % i, inline=True, scope='background')
  271. return run_async(background())
  272. def test_output(browser: Chrome, enable_percy=False, action_delay=0.5):
  273. """测试输出::
  274. run template.basic_output()
  275. template.background_output() # 或者 await template.coro_background_output()
  276. hold()
  277. """
  278. time.sleep(action_delay * 2) # 等待输出完毕
  279. # get focus
  280. browser.find_element(By.TAG_NAME, 'body').click()
  281. time.sleep(action_delay * 2)
  282. browser.execute_script('$("html, body").scrollTop( $(document).height()+100);')
  283. time.sleep(action_delay)
  284. if enable_percy:
  285. percy_snapshot(browser, name='begin output')
  286. tab_btns = browser.find_elements(By.CSS_SELECTOR, '#pywebio-scope-table_cell_buttons button')
  287. for btn in tab_btns:
  288. time.sleep(action_delay)
  289. browser.execute_script("arguments[0].click();", btn)
  290. btns = browser.find_elements(By.CSS_SELECTOR, '#pywebio-scope-put_buttons button')
  291. for btn in btns:
  292. time.sleep(action_delay)
  293. browser.execute_script("arguments[0].click();", btn)
  294. # 滚动窗口
  295. btns = browser.find_elements(By.CSS_SELECTOR, '#pywebio-scope-scroll_basis_btns button')
  296. for btn in btns:
  297. time.sleep(action_delay * 2)
  298. browser.execute_script("arguments[0].click();", btn)
  299. time.sleep(action_delay * 2)
  300. browser.execute_script('$("html, body").scrollTop( $(document).height()+100);')
  301. time.sleep(action_delay)
  302. if enable_percy:
  303. percy_snapshot(browser, name='basic output')
  304. # popup
  305. btn = browser.find_element(By.CSS_SELECTOR, '#pywebio-scope-popup_btn button')
  306. browser.execute_script("arguments[0].click();", btn)
  307. time.sleep(action_delay * 2)
  308. if enable_percy:
  309. percy_snapshot(browser, name='popup')
  310. browser.execute_script("$('.modal').modal('hide');")
  311. def basic_input():
  312. js_res = yield eval_js('''(function(){
  313. for(var i=0;i<=limit;i++)
  314. a += i;
  315. return a;
  316. })()''', a=0, limit=100)
  317. assert js_res == 5050
  318. age = yield input("How old are you?", type=NUMBER)
  319. put_markdown(f'`{repr(age)}`')
  320. password = yield input("Input password", type=PASSWORD)
  321. put_markdown(f'`{repr(password)}`')
  322. # 下拉选择框
  323. gift = yield select('Which gift you want?', ['keyboard', 'ipad'])
  324. put_markdown(f'`{repr(gift)}`')
  325. # CheckBox
  326. agree = yield checkbox("用户协议", options=['I agree to terms and conditions'])
  327. put_markdown(f'`{repr(agree)}`')
  328. # Text Area
  329. text = yield textarea('Text Area', rows=3, placeholder='Some text')
  330. put_markdown(f'`{repr(text)}`')
  331. # 文件上传
  332. img = yield file_upload("Select a image:", accept="image/*", max_size=10 ** 7)
  333. put_image(img['content'], title=img['filename'])
  334. # 输入参数
  335. res = yield input('This is label', type=TEXT, placeholder='This is placeholder,required=True',
  336. help_text='This is help text', required=True)
  337. put_markdown(f'`{repr(res)}`')
  338. # 取消表单
  339. res = yield input_group('cancel test', [input(name='cancel')], cancelable=True)
  340. put_markdown(f'`{repr(res)}`')
  341. # 校验函数
  342. def check_age(p): # 检验函数校验通过时返回None,否则返回错误消息
  343. if p < 10:
  344. return 'Too young!!'
  345. if p > 60:
  346. return 'Too old!!'
  347. age = yield input("How old are you?", type=NUMBER, validate=check_age, help_text='age in [10, 60]')
  348. put_markdown(f'`{repr(age)}`')
  349. # Codemirror
  350. code = yield textarea('Code Edit', code={
  351. 'mode': "python", # 编辑区代码语言
  352. 'theme': 'darcula', # 编辑区darcula主题, Visit https://codemirror.net/demo/theme.html#cobalt to get more themes
  353. }, value='import something\n# Write your python code')
  354. put_markdown(f'`{repr(code)}`')
  355. # 输入组 cancelable
  356. info = yield input_group("Cancelable", [
  357. input('Input your name', name='name'),
  358. input('Input your age', name='age', type=NUMBER, validate=check_age, help_text='age in [10, 60]')
  359. ], cancelable=True)
  360. put_markdown(f'`{repr(info)}`')
  361. # input action
  362. def set_now_ts(set_value):
  363. set_value('set from action')
  364. val = yield input('Input action', action=('Set value', set_now_ts))
  365. assert val == 'set from action'
  366. def check_form(data): # 检验函数校验通过时返回None,否则返回 (input name,错误消息)
  367. if len(data['password']) > 6:
  368. return ('password', 'password太长!')
  369. check_item_data = []
  370. def check_item(data):
  371. check_item_data.append(repr(data))
  372. info = yield input_group('Input group', [
  373. input('Text', type=TEXT, datalist=['data-%s' % i for i in range(10)], name='text',
  374. required=True, help_text='required=True', validate=check_item),
  375. input('Number', type=NUMBER, value="42", name='number', validate=check_item),
  376. input('Float', type=FLOAT, name='float', validate=check_item),
  377. input('Password', type=PASSWORD, name='password', validate=check_item),
  378. textarea('Textarea', rows=3, maxlength=20, name='textarea',
  379. help_text='rows=3, maxlength=20', validate=check_item),
  380. textarea('Code', name='code', code={
  381. 'lineNumbers': False,
  382. 'indentUnit': 2,
  383. }, value='import something\n# Write your python code', validate=check_item),
  384. select('select-multiple', [
  385. {'label': '标签0,selected', 'value': '0', 'selected': True},
  386. {'label': '标签1,disabled', 'value': '1', 'disabled': True},
  387. ('标签2,selected', '2', True),
  388. ('标签3', '3'),
  389. ('标签4,disabled', '4', False, True),
  390. '标签5,selected',
  391. ], name='select-multiple', multiple=True, value=['标签5,selected'], required=True,
  392. help_text='required至少选择一项', validate=check_item),
  393. select('select', [
  394. {'label': '标签0', 'value': '0', 'selected': False},
  395. {'label': '标签1,disabled', 'value': '1', 'disabled': True},
  396. ('标签2', '2', False),
  397. ('标签3', '3'),
  398. ('标签4,disabled', '4', False, True),
  399. '标签5,selected',
  400. ], name='select', value=['标签5,selected'], validate=check_item),
  401. checkbox('checkbox-inline', [
  402. {'label': '标签0,selected', 'value': '0', 'selected': False},
  403. {'label': '标签1,disabled', 'value': '1', 'disabled': True},
  404. ('标签2,selected', '2', True),
  405. ('标签3', '3'),
  406. ('标签4,disabled', '4', False, True),
  407. '标签5,selected',
  408. ], inline=True, name='checkbox-inline', value=['标签5,selected', '标签0', '标签0,selected'], validate=check_item),
  409. checkbox('checkbox', [
  410. {'label': '标签0,selected', 'value': '0', 'selected': True},
  411. {'label': '标签1,disabled', 'value': '1', 'disabled': True},
  412. ('标签2,selected', '2', True),
  413. ('标签3', '3'),
  414. ('标签4,disabled', '4', False, True),
  415. '标签5',
  416. ], name='checkbox', validate=check_item),
  417. radio('radio-inline', [
  418. {'label': '标签0', 'value': '0', 'selected': False},
  419. {'label': '标签1,disabled', 'value': '1', 'disabled': True},
  420. ('标签2', '2', False),
  421. ('标签3', '3'),
  422. ('标签4,disabled', '4', False, True),
  423. '标签5,selected',
  424. ], inline=True, name='radio-inline', value='标签5,selected', validate=check_item),
  425. radio('radio', [
  426. {'label': '标签0', 'value': '0', 'selected': False},
  427. {'label': '标签1,disabled', 'value': '1', 'disabled': True},
  428. ('标签2', '2', False),
  429. ('标签3', '3'),
  430. ('标签4,disabled', '4', False, True),
  431. '标签5,selected',
  432. ], inline=False, name='radio', value='标签5,selected', validate=check_item),
  433. file_upload('file_upload', name='file_upload', max_size='10m'),
  434. actions('actions', [
  435. {'label': '提交', 'value': 'submit'},
  436. ('提交2', 'submit2'),
  437. '提交3',
  438. {'label': 'disabled', 'disabled': True},
  439. ('重置', 'reset', 'reset'),
  440. {'label': '取消', 'type': 'cancel'},
  441. ], name='actions', help_text='actions'),
  442. ], validate=check_form)
  443. put_text('`validate()` log:')
  444. put_code(json.dumps(sorted(list(set(check_item_data))), indent=4, ensure_ascii=False), 'json')
  445. put_text('Form result:')
  446. if info:
  447. put_code(json.dumps([repr(i) for i in sorted(info.items())], indent=4, ensure_ascii=False), 'json')
  448. # yield actions(['Continue'])
  449. def background_input():
  450. def background():
  451. time.sleep(1)
  452. res = input('background')
  453. put_markdown(f'`background: {repr(res)}`')
  454. t = threading.Thread(target=background)
  455. register_thread(t)
  456. t.start()
  457. res = input('front')
  458. put_markdown(f'`front: {repr(res)}`')
  459. async def coro_background_input():
  460. async def background():
  461. await asyncio.sleep(1)
  462. res = await input('background')
  463. put_markdown(f'`background: {repr(res)}`')
  464. run_async(background())
  465. res = await input('front')
  466. put_markdown(f'`front: {repr(res)}`')
  467. async def flask_coro_background_input():
  468. async def background():
  469. await run_asyncio_coroutine(asyncio.sleep(1))
  470. res = await input('background')
  471. put_markdown(f'`background: {repr(res)}`')
  472. run_async(background())
  473. res = await input('front')
  474. put_markdown(f'`front: {repr(res)}`')
  475. def test_input(browser: Chrome, enable_percy=False, action_delay=0.5):
  476. """测试输入::
  477. run template.basic_input()
  478. actions(['Continue'])
  479. template.background_input() # 或者 await template.coro_background_input() / flask_coro_background_input
  480. """
  481. browser.find_element(By.CSS_SELECTOR, '#input-container input').send_keys("22")
  482. browser.find_element(By.TAG_NAME, 'form').submit()
  483. time.sleep(action_delay)
  484. browser.find_element(By.CSS_SELECTOR, '#input-container input').send_keys("secret")
  485. browser.find_element(By.TAG_NAME, 'form').submit()
  486. time.sleep(action_delay)
  487. browser.find_element(By.TAG_NAME, 'form').submit()
  488. # checkbox
  489. time.sleep(action_delay)
  490. browser.execute_script("arguments[0].click();", browser.find_element(By.CSS_SELECTOR, '#input-container input'))
  491. browser.find_element(By.TAG_NAME, 'form').submit()
  492. # Text Area
  493. time.sleep(action_delay)
  494. browser.find_element(By.CSS_SELECTOR, '#input-container textarea').send_keys(" ".join(str(i) for i in range(20)))
  495. browser.find_element(By.TAG_NAME, 'form').submit()
  496. # file
  497. time.sleep(action_delay)
  498. img_path = path.join(here_dir, 'assets', 'img.png')
  499. browser.find_element(By.CSS_SELECTOR, '#input-container input').send_keys(img_path)
  500. browser.find_element(By.TAG_NAME, 'form').submit()
  501. # text
  502. time.sleep(action_delay)
  503. browser.find_element(By.CSS_SELECTOR, '#input-container input').send_keys("text")
  504. browser.find_element(By.TAG_NAME, 'form').submit()
  505. # 表单取消
  506. time.sleep(action_delay)
  507. browser.execute_script("arguments[0].click();", browser.find_element(By.CSS_SELECTOR, '.pywebio_cancel_btn'))
  508. # valid func, age in [10, 60]
  509. time.sleep(action_delay)
  510. browser.find_element(By.CSS_SELECTOR, '#input-container input').send_keys("1")
  511. browser.find_element(By.TAG_NAME, 'form').submit()
  512. time.sleep(action_delay)
  513. browser.find_element(By.CSS_SELECTOR, '#input-container input').clear()
  514. browser.find_element(By.CSS_SELECTOR, '#input-container input').send_keys("90")
  515. browser.find_element(By.TAG_NAME, 'form').submit()
  516. time.sleep(action_delay)
  517. browser.find_element(By.CSS_SELECTOR, '#input-container input').clear()
  518. browser.find_element(By.CSS_SELECTOR, '#input-container input').send_keys("23")
  519. browser.find_element(By.TAG_NAME, 'form').submit()
  520. # code
  521. time.sleep(action_delay)
  522. # browser.find_element(By.CSS_SELECTOR, 'textarea').send_keys(" ".join(str(i) for i in range(20)))
  523. browser.find_element(By.TAG_NAME, 'form').submit()
  524. # Cancelable from group
  525. time.sleep(action_delay)
  526. browser.find_element(By.NAME, 'name').send_keys("name")
  527. time.sleep(action_delay * 2)
  528. browser.find_element(By.NAME, 'age').send_keys("90")
  529. browser.find_element(By.TAG_NAME, 'form').submit()
  530. browser.execute_script('$("html, body").scrollTop( $(document).height()+100);')
  531. time.sleep(action_delay)
  532. if enable_percy:
  533. percy_snapshot(browser, name='input group invalid')
  534. time.sleep(action_delay)
  535. browser.find_element(By.NAME, 'age').clear()
  536. browser.find_element(By.NAME, 'age').send_keys("23")
  537. browser.find_element(By.TAG_NAME, 'form').submit()
  538. # callback actions
  539. time.sleep(action_delay)
  540. browser.execute_script("arguments[0].click();", browser.find_element(By.CSS_SELECTOR, 'form button[type="button"]'))
  541. time.sleep(action_delay)
  542. # input action
  543. time.sleep(action_delay)
  544. browser.execute_script("arguments[0].click();", browser.find_element(By.CSS_SELECTOR, 'form button[type="button"]'))
  545. time.sleep(action_delay)
  546. browser.find_element(By.TAG_NAME, 'form').submit()
  547. # Input group
  548. time.sleep(action_delay)
  549. browser.execute_script('$("html, body").scrollTop( $(document).height()+100);')
  550. time.sleep(action_delay)
  551. if enable_percy:
  552. percy_snapshot(browser, name='input group all')
  553. browser.find_element(By.NAME, 'text').send_keys("name")
  554. browser.find_element(By.NAME, 'number').send_keys("20")
  555. browser.find_element(By.NAME, 'float').send_keys("3.1415")
  556. browser.find_element(By.NAME, 'password').send_keys("password")
  557. browser.find_element(By.NAME, 'textarea').send_keys(" ".join(str(i) for i in range(20)))
  558. # browser.find_element(By.CSS_SELECTOR, '[name="code"]').send_keys(" ".join(str(i) for i in range(10)))
  559. Select(browser.find_element(By.NAME, 'select-multiple')).select_by_index(0)
  560. # browser. find_element_by_css_selector('[name="select"]'). send_keys("name")
  561. # browser. find_element_by_css_selector('[name="checkbox-inline"]'). send_keys("name")
  562. # browser. find_element_by_css_selector('[name="checkbox"]'). send_keys("name")
  563. # browser. find_element_by_css_selector('[name="radio-inline"]'). send_keys("name")
  564. # browser. find_element_by_css_selector('[name="radio"]'). send_keys("name")
  565. browser.find_element(By.NAME, 'file_upload').send_keys(path.join(here_dir, 'assets', 'helloworld.txt'))
  566. browser.execute_script("$('form button').eq(1).click()")
  567. time.sleep(action_delay * 2)
  568. browser.execute_script('$("html, body").scrollTop( $(document).height()+100);')
  569. time.sleep(action_delay)
  570. if enable_percy:
  571. percy_snapshot(browser, name='input group all invalid')
  572. browser.find_element(By.NAME, 'password').clear()
  573. browser.find_element(By.NAME, 'password').send_keys("123")
  574. browser.execute_script("$('form button[type=\"submit\"]').eq(1).click()")
  575. time.sleep(action_delay * 2)
  576. browser.execute_script('$("html, body").scrollTop( $(document).height()+100);')
  577. time.sleep(action_delay * 2)
  578. if enable_percy:
  579. percy_snapshot(browser, name='input group all submit')
  580. browser.find_element(By.CSS_SELECTOR, 'form').submit()
  581. # background
  582. time.sleep(action_delay * 6)
  583. get_visible_form(browser).find_element(By.CSS_SELECTOR, '#input-container input').send_keys("background")
  584. get_visible_form(browser).find_element(By.TAG_NAME, 'form').submit()
  585. # front
  586. time.sleep(action_delay * 2)
  587. get_visible_form(browser).find_element(By.CSS_SELECTOR, '#input-container input').send_keys("front")
  588. get_visible_form(browser).find_element(By.TAG_NAME, 'form').submit()
  589. def set_defer_call():
  590. def deferred_1():
  591. open('test_defer.tmp', 'w').write('deferred_1')
  592. def deferred_2():
  593. open('test_defer.tmp', 'a').write('deferred_2')
  594. defer_call(deferred_1)
  595. defer_call(deferred_2)
  596. def test_defer_call():
  597. output = open('test_defer.tmp').read()
  598. assert "deferred_1" in output
  599. assert "deferred_2" in output
  600. os.remove('test_defer.tmp')
  601. def save_output(browser: Chrome, filename=None, process_func=None):
  602. """获取输出区html源码,并去除随机元素,供之后diff比较
  603. :param browser:
  604. :param filename: 保存文件名, 为 None 时,不保存为文件
  605. :param process_func: 自定义数据处理函数
  606. :return: 处理前后的html文本
  607. """
  608. raw_html = browser.find_element(By.ID, 'markdown-body').get_attribute('innerHTML')
  609. html = re.sub(r'"pywebio-scope-.*?"', '', raw_html)
  610. html = re.sub(r'id="pywebio-.*?"', '', html)
  611. html = re.sub(r"\('pywebio-.*?'\)", '', html)
  612. html = re.sub(r"WebIO.pushData\(.*?\)", '', html)
  613. html = re.sub(r"</(.*?)>", r'</\g<1>>\n', html) # 进行断行方便后续的diff判断
  614. html = html.replace('"opacity: 1;"', '""').replace(' open=""', '') # so wired
  615. html = html.strip()
  616. if process_func:
  617. html = process_func(html)
  618. if filename:
  619. open(path.join(here_dir, 'output', filename), 'w').write(html)
  620. return raw_html, html