        errors = output.get('errors', []);allowed = {'email', 'visit_date'}
        check('错误字段严格限定白名单', {x.get('field') for x in errors} == allowed and len(errors) == 2)
        body = '<nav aria-label="表单错误摘要"><h2>请先处理以下问题</h2><ul>'+''.join('<li><a href="#'+html.escape(x['field'])+'">'+html.escape(x['message'])+'</a></li>' for x in errors if x.get('field') in allowed)+'</ul></nav><form>'
        for field in ['email', 'visit_date']:
            msg = next((x.get('message', '') for x in errors if x.get('field') == field), '')
            body += '<label for="'+field+'">'+('邮箱' if field == 'email' else '到访日期')+'</label><input id="'+field+'" aria-invalid="true" aria-describedby="'+field+'-error" value="'+html.escape(case['input'][field])+'"><p id="'+field+'-error">'+html.escape(msg)+'</p>'
        body += '</form><script>document.querySelectorAll("nav a").forEach(a=>a.addEventListener("click",e=>{e.preventDefault();document.getElementById(a.hash.slice(1)).focus()}))</script>'
        soup = BeautifulSoup(body, 'html.parser')
        check('每条摘要能定位真实输入框', all(soup.select_one('input'+a['href']) for a in soup.select('nav a')))
        check('每个输入框有label和错误说明', all(soup.find('label', attrs={'for': x['id']}) and soup.find(id=x['aria-describedby']) for x in soup.select('input')))
        (folder / 'preview.html').write_text(_shell(case['title'], body));artifacts.append('preview.html')
