chore(scripts): 新增二维码与联调诊断脚本
排查过程中沉淀的一次性诊断工具,保留以便后续复现同类问题。
- CompareQrRender / DecodeQr / DiagnoseQrFailures:判定「解不开的标签」
是标签本身损坏还是解码器局限(zxing 编解码复现 + 渲染结果逐像素比对)
- diag-login / diag-bagcode / diag-pill:Playwright 页面结构 dump,
用于校准冒烟测试选择器、区分前端缺陷与测试选择器问题
- split-hunks.py:从 git diff 中精确提取指定 hunk,用于非交互式精确暂存
scripts/qr-export-test-out/ 为上述诊断产生的截图产物,不入库。
Co-Authored-By: Claude <noreply@anthropic.com>
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import com.google.zxing.BarcodeFormat; |
| | | import com.google.zxing.EncodeHintType; |
| | | import com.google.zxing.common.BitMatrix; |
| | | import com.google.zxing.qrcode.QRCodeWriter; |
| | | import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; |
| | | |
| | | import javax.imageio.ImageIO; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.File; |
| | | import java.util.EnumMap; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * å¤å®"è§£ä¸å¼çæ ç¾"å°åºæ¯æ ç¾åäºï¼è¿æ¯è§£ç å¨çå±é |
| | | * |
| | | * ç¨æ³ï¼java CompareQrRender <æ ç¾ç®å½> <æä»¶å1> [æä»¶å2 ...] |
| | | * |
| | | * åæ³ï¼ç¨ä¸å¯¼åºå®å
¨ç¸åçç¼ç åæ°ï¼çº éçº§å« Hãéåº 4ãè¾¹é¿ 260ï¼éæ°çæåä¸ç å¼çäºç»´ç ï¼ |
| | | * å䏿 ç¾å¾çä¸çäºç»´ç åºåéåç´ æ¯å¯¹ã |
| | | * - å®å
¨ä¸è´ â æ ç¾æ¯è¯¥ç å¼çå¿ å®æ¸²æï¼äºç»´ç æ°æ®ææï¼è§£ä¸åºæ¯è§£ç å¨çé®é¢ |
| | | * - åå¨å·®å¼ â æ ç¾æ¸²æç¯èç¡®å®åºäºé®é¢ |
| | | */ |
| | | public class CompareQrRender { |
| | | |
| | | /** ä¸ QrCodeImageUtils ä¿æä¸è´ */ |
| | | private static final int PADDING = 16; |
| | | private static final int QR_SIZE = 260; |
| | | /** ä¸ MesProQrLabelBuilder ä¸è´ï¼äºç»´ç å
容为追溯ç½å + code åæ° */ |
| | | private static final String TRACE_BASE = "http://192.168.0.10:48080/trace.html"; |
| | | |
| | | public static void main(String[] args) throws Exception { |
| | | File dir = new File(args[0]); |
| | | for (int i = 1; i < args.length; i++) { |
| | | File f = new File(dir, args[i]); |
| | | String name = args[i]; |
| | | String code = name.endsWith(".png") ? name.substring(0, name.length() - 4) : name; |
| | | BufferedImage label = ImageIO.read(f); |
| | | if (label == null) { |
| | | System.out.println(name + " -> æ æ³è¯»å"); |
| | | continue; |
| | | } |
| | | BufferedImage expect = generate(code); |
| | | BufferedImage actual = label.getSubimage(PADDING, PADDING, QR_SIZE, QR_SIZE); |
| | | if (expect.getWidth() != actual.getWidth() || expect.getHeight() != actual.getHeight()) { |
| | | System.out.println(name + " -> 尺寸ä¸ä¸è´ï¼æ æ³æ¯å¯¹"); |
| | | continue; |
| | | } |
| | | long diff = 0; |
| | | for (int y = 0; y < QR_SIZE; y++) { |
| | | for (int x = 0; x < QR_SIZE; x++) { |
| | | if (expect.getRGB(x, y) != actual.getRGB(x, y)) { |
| | | diff++; |
| | | } |
| | | } |
| | | } |
| | | long total = (long) QR_SIZE * QR_SIZE; |
| | | System.out.printf("%s%n éæ°çæä¸æ ç¾äºç»´ç åºåéåç´ æ¯å¯¹ï¼ä¸ä¸è´ %d / %d (%.4f%%)%n", |
| | | name, diff, total, diff * 100.0 / total); |
| | | System.out.println(" å¤å®ï¼" + (diff == 0 |
| | | ? "æ ç¾ä¸ºè¯¥ç å¼çå¿ å®æ¸²æ â äºç»´ç æ°æ®ææï¼è§£ä¸åºå±è§£ç 端é®é¢" |
| | | : "æ ç¾æ¸²æä¸é¢æä¸ç¬¦ â æ¸²æç¯èåå¨é®é¢")); |
| | | } |
| | | } |
| | | |
| | | private static BufferedImage generate(String code) throws Exception { |
| | | String content = TRACE_BASE + "?code=" + code; |
| | | Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class); |
| | | hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); |
| | | hints.put(EncodeHintType.MARGIN, 4); |
| | | hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); |
| | | BitMatrix matrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, QR_SIZE, QR_SIZE, hints); |
| | | int w = matrix.getWidth(); |
| | | int h = matrix.getHeight(); |
| | | BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); |
| | | for (int y = 0; y < h; y++) { |
| | | for (int x = 0; x < w; x++) { |
| | | img.setRGB(x, y, matrix.get(x, y) ? 0x000000 : 0xFFFFFF); |
| | | } |
| | | } |
| | | return img; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import com.google.zxing.BarcodeFormat; |
| | | import com.google.zxing.BinaryBitmap; |
| | | import com.google.zxing.DecodeHintType; |
| | | import com.google.zxing.LuminanceSource; |
| | | import com.google.zxing.MultiFormatReader; |
| | | import com.google.zxing.NotFoundException; |
| | | import com.google.zxing.Result; |
| | | import com.google.zxing.client.j2se.BufferedImageLuminanceSource; |
| | | import com.google.zxing.common.GlobalHistogramBinarizer; |
| | | import com.google.zxing.common.HybridBinarizer; |
| | | |
| | | import javax.imageio.ImageIO; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.File; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.EnumMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * äºç»´ç æ ç¾è§£ç æ ¡éª |
| | | * |
| | | * ç¨æ³ï¼java DecodeQr <æ ç¾ç®å½> |
| | | * |
| | | * 对æ¯å¼ æ ç¾åå«ç¨ãæ´å¾ãåãè£å°äºç»´ç åºåãä¸¤ç§æ¹å¼è§£ç ï¼ |
| | | * æ´å¾å¤±è´¥èè£å¾æåï¼è¯´ææ ç¾æ¬èº«æ²¡åï¼æ¯ä¸æ¹æåå¹²æ°äºæ£æµå¨ï¼å±è§£ç 端é®é¢ï¼ |
| | | * ä¸¤ç§æ¹å¼é½å¤±è´¥ï¼ææ¯æ ç¾ççä¸å¯è¯è¯»ã |
| | | */ |
| | | public class DecodeQr { |
| | | |
| | | /** æ ç¾æçåæ°ï¼é¡»ä¸ QrCodeImageUtils ä¿æä¸è´ */ |
| | | private static final int PADDING = 16; |
| | | private static final int QR_SIZE = 260; |
| | | /** è£å¾æ¶åå¤å¤ççä½éï¼ä¿è¯äºç»´ç éåºä¸è¢«åæ */ |
| | | private static final int CROP_MARGIN = 10; |
| | | |
| | | public static void main(String[] args) throws Exception { |
| | | if (args.length == 0) { |
| | | System.out.println("ç¨æ³: java DecodeQr <æ ç¾ç®å½>"); |
| | | return; |
| | | } |
| | | File dir = new File(args[0]); |
| | | File[] files = dir.listFiles((d, n) -> n.toLowerCase().endsWith(".png")); |
| | | if (files == null || files.length == 0) { |
| | | System.out.println("ç®å½å
没æ PNG æä»¶ï¼" + dir.getAbsolutePath()); |
| | | return; |
| | | } |
| | | Arrays.sort(files); |
| | | |
| | | int fullOk = 0; |
| | | int cropOk = 0; |
| | | int bothFail = 0; |
| | | List<String> fullFailCropOk = new ArrayList<>(); |
| | | List<String> hardFail = new ArrayList<>(); |
| | | List<String> mismatched = new ArrayList<>(); |
| | | String sampleContent = null; |
| | | |
| | | for (File f : files) { |
| | | String name = f.getName(); |
| | | String code = name.endsWith(".png") ? name.substring(0, name.length() - 4) : name; |
| | | BufferedImage image = ImageIO.read(f); |
| | | if (image == null) { |
| | | hardFail.add(name + " -> å¾çæ æ³è¯»å"); |
| | | bothFail++; |
| | | continue; |
| | | } |
| | | String full = decode(image); |
| | | String crop = decode(cropQrArea(image)); |
| | | if (full != null) { |
| | | fullOk++; |
| | | } |
| | | if (crop != null) { |
| | | cropOk++; |
| | | } |
| | | if (full == null && crop == null) { |
| | | bothFail++; |
| | | hardFail.add(name + " -> æ´å¾ä¸è£å¾åè§£ç 失败"); |
| | | continue; |
| | | } |
| | | if (full == null) { |
| | | fullFailCropOk.add(name); |
| | | } |
| | | String text = full != null ? full : crop; |
| | | if (!text.contains(code)) { |
| | | mismatched.add(name + " -> å
容ä¸ç å¼ä¸ç¬¦: " + text); |
| | | } |
| | | if (sampleContent == null) { |
| | | sampleContent = "ç å¼ " + code + "\n äºç»´ç å
容 " + text; |
| | | } |
| | | } |
| | | |
| | | int total = files.length; |
| | | System.out.println("æ ç¾æ»æ° : " + total); |
| | | System.out.printf ("æ´å¾è§£ç æå : %d (%.2f%%)%n", fullOk, fullOk * 100.0 / total); |
| | | System.out.printf ("è£å¾è§£ç æå : %d (%.2f%%)%n", cropOk, cropOk * 100.0 / total); |
| | | System.out.printf ("ä¸¤ç§æ¹å¼é½å¤±è´¥ : %d (%.2f%%)%n", bothFail, bothFail * 100.0 / total); |
| | | System.out.println("æ´å¾å¤±è´¥ä½è£å¾æåï¼æ£æµå䏿¹æåå¹²æ°ï¼æ ç¾æ¬èº«æ£å¸¸ï¼: " + fullFailCropOk.size()); |
| | | System.out.println("\n--- æ ·æ¬ ---\n " + sampleContent); |
| | | |
| | | if (!fullFailCropOk.isEmpty()) { |
| | | System.out.println("\n--- ä»
æ´å¾å¤±è´¥çç ï¼å 10ï¼ ---"); |
| | | fullFailCropOk.stream().limit(10).forEach(s -> System.out.println(" " + s)); |
| | | } |
| | | if (!mismatched.isEmpty()) { |
| | | System.out.println("\n!!! å
容ä¸ç å¼ä¸ç¬¦ï¼å 10ï¼ !!!"); |
| | | mismatched.stream().limit(10).forEach(s -> System.out.println(" " + s)); |
| | | } |
| | | if (!hardFail.isEmpty()) { |
| | | System.out.println("\n--- çæ£ä¸å¯è¯è¯»çæ ç¾ï¼å 20ï¼ ---"); |
| | | hardFail.stream().limit(20).forEach(s -> System.out.println(" " + s)); |
| | | } |
| | | } |
| | | |
| | | /** è£åºäºç»´ç æå¨åºåï¼å»æä¸æ¹å å çæå */ |
| | | private static BufferedImage cropQrArea(BufferedImage src) { |
| | | int x = Math.max(0, PADDING - CROP_MARGIN); |
| | | int y = Math.max(0, PADDING - CROP_MARGIN); |
| | | int w = Math.min(src.getWidth() - x, QR_SIZE + CROP_MARGIN * 2); |
| | | int h = Math.min(src.getHeight() - y, QR_SIZE + CROP_MARGIN * 2); |
| | | return src.getSubimage(x, y, w, h); |
| | | } |
| | | |
| | | /** å°è¯å¤ç§äºå¼åçç¥è§£ç ï¼å
¨é¨å¤±è´¥è¿å null */ |
| | | private static String decode(BufferedImage image) { |
| | | LuminanceSource source = new BufferedImageLuminanceSource(image); |
| | | Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class); |
| | | hints.put(DecodeHintType.CHARACTER_SET, "UTF-8"); |
| | | hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); |
| | | hints.put(DecodeHintType.POSSIBLE_FORMATS, List.of(BarcodeFormat.QR_CODE)); |
| | | // HybridBinarizer éåå
ç
§ä¸åï¼GlobalHistogramBinarizer 对é«å¯¹æ¯åº¦å¾æ´ç¨³ï¼ä¸¤è
äºè¡¥ |
| | | for (var binarizer : List.of(new HybridBinarizer(source), new GlobalHistogramBinarizer(source))) { |
| | | try { |
| | | Result r = new MultiFormatReader().decode(new BinaryBitmap(binarizer), hints); |
| | | if (r != null && r.getText() != null) { |
| | | return r.getText(); |
| | | } |
| | | } catch (NotFoundException ignored) { |
| | | // æ¢ä¸ä¸ç§çç¥ |
| | | } catch (Exception ignored) { |
| | | // åä¸ |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import com.google.zxing.BarcodeFormat; |
| | | import com.google.zxing.BinaryBitmap; |
| | | import com.google.zxing.DecodeHintType; |
| | | import com.google.zxing.LuminanceSource; |
| | | import com.google.zxing.MultiFormatReader; |
| | | import com.google.zxing.Result; |
| | | import com.google.zxing.client.j2se.BufferedImageLuminanceSource; |
| | | import com.google.zxing.common.HybridBinarizer; |
| | | |
| | | import javax.imageio.ImageIO; |
| | | import java.awt.RenderingHints; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.File; |
| | | import java.util.EnumMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * è§£ç å¤±è´¥æ ·æ¬çæå å¤å® |
| | | * |
| | | * ç¨æ³ï¼java DiagnoseQrFailures <æ ç¾ç®å½> <æä»¶å1> [æä»¶å2 ...] |
| | | * |
| | | * æè·¯ï¼æå¾çææ´æ°åæè¿é»æ¾å¤§åéæ°è§£ç ãäºç»´ç æ¯çº¯é»ç½æ¹åï¼ |
| | | * æè¿é»æ¾å¤§ä¸å¼å
¥æ°ä¿¡æ¯ï¼åªæ¯ç»è§£ç å¨çéæ ·ç½æ ¼æ´å¤ä½å°ã |
| | | * - æ¾å¤§åè½è§£åº â äºç»´ç æ°æ®æ¬èº«æ¯å¯¹çï¼åªæ¯æ¨¡åå¯åº¦/éæ ·ä½ç½®è¿äºä¸´ç |
| | | * ï¼å±è§£ç 端é®é¢ï¼ä½å°å·å°ºå¯¸åå°æ¶ç宿«ç æªä¹å¯è½è¯»ä¸åºï¼åºç¼©çäºç»´ç å
容æå 大æ ç¾ï¼ |
| | | * - æ¾å¤§åä»è§£ä¸åº â 该æ ç¾ç¡®æç¼ºé·ï¼éæåºåå¤ç |
| | | */ |
| | | public class DiagnoseQrFailures { |
| | | |
| | | public static void main(String[] args) throws Exception { |
| | | File dir = new File(args[0]); |
| | | for (int i = 1; i < args.length; i++) { |
| | | File f = new File(dir, args[i]); |
| | | if (!f.isFile()) { |
| | | System.out.println(args[i] + " -> æä»¶ä¸åå¨"); |
| | | continue; |
| | | } |
| | | BufferedImage img = ImageIO.read(f); |
| | | String at1 = decode(img); |
| | | System.out.println("\n=== " + args[i] + " ==="); |
| | | System.out.println(" åå§å°ºå¯¸ : " + img.getWidth() + " x " + img.getHeight()); |
| | | System.out.println(" 1x è§£ç : " + (at1 == null ? "失败" : "æå -> " + at1)); |
| | | if (at1 != null) { |
| | | continue; |
| | | } |
| | | boolean rescued = false; |
| | | for (int scale : List.of(2, 3, 4, 6)) { |
| | | BufferedImage up = scaleNearest(img, scale); |
| | | String text = decode(up); |
| | | System.out.println(" " + scale + "x æ¾å¤§è§£ç : " + (text == null ? "失败" : "æå -> " + text)); |
| | | if (text != null) { |
| | | rescued = true; |
| | | break; |
| | | } |
| | | } |
| | | System.out.println(" ç»è®º : " + (rescued |
| | | ? "äºç»´ç æ°æ®ææï¼å±æ¨¡åå¯åº¦ä¸´çï¼è§£ç 端ï¼ï¼éåºç " |
| | | : "æ¾å¤§åä»ä¸å¯è¯è¯»ï¼æ ç¾ç¡®æç¼ºé·")); |
| | | } |
| | | } |
| | | |
| | | /** æ´æ°åæè¿é»æ¾å¤§ï¼ä¸æå¼ãä¸å¼å
¥æ°åç´ å¼ï¼åªæ¾å¤§éæ ·ç©ºé´ */ |
| | | private static BufferedImage scaleNearest(BufferedImage src, int scale) { |
| | | BufferedImage out = new BufferedImage( |
| | | src.getWidth() * scale, src.getHeight() * scale, BufferedImage.TYPE_INT_RGB); |
| | | var g = out.createGraphics(); |
| | | try { |
| | | g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); |
| | | g.drawImage(src, 0, 0, out.getWidth(), out.getHeight(), null); |
| | | } finally { |
| | | g.dispose(); |
| | | } |
| | | return out; |
| | | } |
| | | |
| | | private static String decode(BufferedImage image) { |
| | | LuminanceSource source = new BufferedImageLuminanceSource(image); |
| | | Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class); |
| | | hints.put(DecodeHintType.CHARACTER_SET, "UTF-8"); |
| | | hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); |
| | | hints.put(DecodeHintType.POSSIBLE_FORMATS, List.of(BarcodeFormat.QR_CODE)); |
| | | try { |
| | | Result r = new MultiFormatReader().decode( |
| | | new BinaryBitmap(new HybridBinarizer(source)), hints); |
| | | return r == null ? null : r.getText(); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /** è¢ç é¡µç»æè¯æï¼ç»å½å dump æç´¢é¡¹ä¸å·¥å
·æ æé®ï¼ç¨äºæ ¡åæµè¯éæ©å¨ */ |
| | | const { chromium } = require('playwright'); |
| | | |
| | | const BASE = 'http://127.0.0.1:5666'; |
| | | |
| | | (async () => { |
| | | const browser = await chromium.launch({ headless: true }); |
| | | const page = await browser.newPage({ viewport: { width: 1600, height: 900 } }); |
| | | page.on('pageerror', (e) => console.log('pageerror:', e.message)); |
| | | |
| | | await page.goto(`${BASE}/auth/login`, { waitUntil: 'domcontentloaded' }); |
| | | await page.locator('input[name=username]').waitFor({ timeout: 60_000 }); |
| | | await page.locator('input[name=username]').fill('admin'); |
| | | await page.locator('input[name=password]').fill('admin123'); |
| | | await page.locator('button:has-text("ç»å½")').last().click(); |
| | | await page.waitForURL((u) => !u.pathname.includes('/auth/login'), { timeout: 60_000 }); |
| | | console.log('ç»å½å URL:', page.url()); |
| | | await page.waitForTimeout(3000); |
| | | |
| | | console.log('\n--- èå项ï¼å«âè¢ç /èµç âçï¼---'); |
| | | for (const el of await page.locator('a, span, div').all()) { |
| | | const t = (await el.innerText().catch(() => '')).trim(); |
| | | if (t && t.length < 20 && /è¢ç |èµç |ä¸è¢ä¸ç /.test(t)) { |
| | | console.log(JSON.stringify(t)); |
| | | } |
| | | } |
| | | |
| | | for (const target of ['/bagcode/bag-code', '/bagcode', '/mes/pro/bagCode']) { |
| | | await page.goto(`${BASE}${target}`, { waitUntil: 'domcontentloaded' }); |
| | | await page.waitForTimeout(4000); |
| | | const url = page.url(); |
| | | const hasExport = (await page.getByRole('button', { name: '导åºäºç»´ç ' }).count()) > 0; |
| | | console.log(`\n>>> å°è¯ ${target} â å®é
${url}ï¼å¯¼åºæé®æ°=${hasExport}`); |
| | | if (!hasExport) continue; |
| | | |
| | | console.log('--- æç´¢è¾å
¥æ¡ placeholder ---'); |
| | | for (const el of await page.locator('input').all()) { |
| | | const ph = await el.getAttribute('placeholder'); |
| | | if (ph && (await el.isVisible().catch(() => false))) console.log(JSON.stringify(ph)); |
| | | } |
| | | console.log('--- å
¨é¨å¯è§æé®ææ¬ ---'); |
| | | for (const el of await page.locator('button').all()) { |
| | | const t = (await el.innerText().catch(() => '')).trim(); |
| | | if (t && (await el.isVisible().catch(() => false))) console.log(JSON.stringify(t)); |
| | | } |
| | | break; |
| | | } |
| | | |
| | | await browser.close(); |
| | | })(); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /** ç»å½é¡µç»æè¯æï¼æå®é
渲æåºæ¥ç表åå
ç´ å
¨é¨æåºæ¥ */ |
| | | const { chromium } = require('playwright'); |
| | | |
| | | (async () => { |
| | | const browser = await chromium.launch({ headless: true }); |
| | | const page = await browser.newPage({ viewport: { width: 1600, height: 900 } }); |
| | | const errors = []; |
| | | page.on('pageerror', (e) => errors.push('pageerror: ' + e.message)); |
| | | page.on('console', (m) => { |
| | | if (m.type() === 'error') errors.push('console: ' + m.text()); |
| | | }); |
| | | page.on('requestfailed', (r) => |
| | | errors.push('reqfail: ' + r.url() + ' ' + (r.failure() || {}).errorText), |
| | | ); |
| | | |
| | | await page.goto('http://127.0.0.1:5666/auth/login', { waitUntil: 'domcontentloaded' }); |
| | | await page.waitForTimeout(8000); |
| | | |
| | | console.log('URL:', page.url()); |
| | | console.log('TITLE:', await page.title()); |
| | | const bodyText = (await page.locator('body').innerText().catch(() => '')).slice(0, 800); |
| | | console.log('--- å¯è§ææ¬ ---'); |
| | | console.log(bodyText); |
| | | |
| | | console.log('--- input å
ç´ ---'); |
| | | for (const el of await page.locator('input').all()) { |
| | | console.log( |
| | | JSON.stringify({ |
| | | type: await el.getAttribute('type'), |
| | | name: await el.getAttribute('name'), |
| | | autocomplete: await el.getAttribute('autocomplete'), |
| | | placeholder: await el.getAttribute('placeholder'), |
| | | id: await el.getAttribute('id'), |
| | | visible: await el.isVisible().catch(() => false), |
| | | }), |
| | | ); |
| | | } |
| | | |
| | | console.log('--- button/role=radio å
ç´ ---'); |
| | | for (const el of await page.locator('button, [role=radio], .ant-radio-wrapper').all()) { |
| | | const t = (await el.innerText().catch(() => '')).trim().slice(0, 40); |
| | | if (t) console.log(JSON.stringify({ tag: await el.evaluate((n) => n.tagName), text: t })); |
| | | } |
| | | |
| | | console.log('--- é误 ---'); |
| | | console.log(errors.slice(0, 20).join('\n') || '(æ )'); |
| | | await page.screenshot({ |
| | | path: require('node:path').join(__dirname, 'qr-export-test-out', 'diag-login.png'), |
| | | }); |
| | | await browser.close(); |
| | | })(); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /** æµ®æ å±å¼è¯æï¼å¤æãç¹æµ®æ æ¢å¤é¢æ¿ã失败æ¯å端 bug è¿æ¯æµè¯éæ©å¨é®é¢ */ |
| | | const { chromium } = require('playwright'); |
| | | |
| | | const BASE = 'http://127.0.0.1:5666'; |
| | | const UUID = 'ae1229655a3e48d7b4bc842e3e74e15c'; |
| | | |
| | | (async () => { |
| | | const browser = await chromium.launch({ headless: true }); |
| | | const page = await browser.newPage({ viewport: { width: 1600, height: 900 } }); |
| | | await page.addInitScript(() => { |
| | | Object.defineProperty(window, 'showSaveFilePicker', { value: undefined, configurable: true }); |
| | | }); |
| | | |
| | | await page.goto(`${BASE}/auth/login`, { waitUntil: 'domcontentloaded' }); |
| | | await page.locator('input[name=username]').waitFor({ timeout: 60_000 }); |
| | | await page.locator('input[name=username]').fill('admin'); |
| | | await page.locator('input[name=password]').fill('admin123'); |
| | | await page.locator('button:has-text("ç»å½")').last().click(); |
| | | await page.waitForURL((u) => !u.pathname.includes('/auth/login'), { timeout: 60_000 }); |
| | | |
| | | await page.goto(`${BASE}/bagcode/bag-code`, { waitUntil: 'domcontentloaded' }); |
| | | await page.getByRole('button', { name: '导åºäºç»´ç ' }).first().waitFor({ timeout: 60_000 }); |
| | | await page.getByPlaceholder('请è¾å
¥çæç»ç¼å·').fill(UUID); |
| | | const searchBtn = page.locator('button').filter({ hasText: /æ\s*ç´¢/ }); |
| | | await searchBtn.first().click(); |
| | | await page.waitForTimeout(3000); |
| | | await page.getByRole('button', { name: '导åºäºç»´ç ' }).first().click(); |
| | | await page.locator('text=æ£å¨çæäºç»´ç ').waitFor({ timeout: 90_000 }); |
| | | console.log('颿¿å·²åºç°'); |
| | | |
| | | // ç¹éè |
| | | const hideBtn = page.locator('button').filter({ hasText: /é\s*è/ }); |
| | | console.log('éèæé®æ°:', await hideBtn.count()); |
| | | await hideBtn.first().click(); |
| | | await page.waitForTimeout(1500); |
| | | |
| | | // æ¾åºææå«ãäºç»´ç 导åºãç divï¼æå°å±çº§ä¸ä½ç½® |
| | | const cands = page.locator('div').filter({ hasText: /äºç»´ç 导åº/ }); |
| | | const n = await cands.count(); |
| | | console.log('\nå«ãäºç»´ç 导åºãç div æ°é:', n); |
| | | for (let i = 0; i < n; i++) { |
| | | const el = cands.nth(i); |
| | | const info = await el.evaluate((node) => ({ |
| | | cls: node.className?.toString().slice(0, 70), |
| | | text: (node.innerText || '').replace(/\s+/g, ' ').slice(0, 40), |
| | | depth: (() => { let d = 0, p = node; while (p.parentElement) { d++; p = p.parentElement; } return d; })(), |
| | | })); |
| | | console.log(` [${i}] depth=${info.depth} cls="${info.cls}" text="${info.text}"`); |
| | | } |
| | | |
| | | const pill = cands.last(); |
| | | console.log('\næç¹å» last() å
ç´ ï¼å
¶ class:', await pill.evaluate((n) => n.className?.toString().slice(0, 70))); |
| | | await pill.click(); |
| | | await page.waitForTimeout(2000); |
| | | |
| | | const bodyText = await page.locator('body').innerText(); |
| | | console.log('\nç¹å»åï¼'); |
| | | console.log(' body å«ãæ£å¨çæäºç»´ç ã:', bodyText.includes('æ£å¨çæäºç»´ç ')); |
| | | console.log(' body å«ãäºç»´ç 导åºã:', bodyText.includes('äºç»´ç 导åº')); |
| | | console.log(' éèæé®æ¯å¦è¿å¨:', await page.locator('button').filter({ hasText: /é\s*è/ }).count()); |
| | | const m = bodyText.match(/æ£å¨çæï¼\d+\s*\/\s*\d+ï¼/); |
| | | console.log(' è¿åº¦ææ¬:', m ? m[0] : '(æªæ¾å°)'); |
| | | |
| | | await browser.close(); |
| | | })(); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | """ä» git diff è¡¥ä¸ä¸åªä¿çæå®åºå·ç hunkï¼ç¨äºç²¾ç¡®æåï¼git add -p æ¯äº¤äºå¼çï¼èæ¬éç¨ä¸äºï¼ã |
| | | |
| | | ç¨æ³: |
| | | python split-hunks.py <è¡¥ä¸æä»¶> <ä¿ççhunkåºå·,éå·åé> [--list] |
| | | --list åªååº hunk åºå·ä¸é¦è¡ï¼ä¸è¾åºè¡¥ä¸ |
| | | """ |
| | | import sys |
| | | |
| | | # å¿
é¡»å¼ºå¶ UTF-8 ä¸ä¸åæ¢è¡ç¿»è¯ï¼å¦å䏿ä¸ä¸æè¡ä¼è¢«æ GBK ååã\n è¢«åæ \r\nï¼ |
| | | # 两è
é½ä¼è®© git apply å¹é
ä¸ä¸ï¼çº¯ ASCII çè¡¥ä¸ä¸ä¼æ´é²è¿ä¸ªé®é¢ï¼å®¹æè¯¯å¤ï¼ |
| | | try: |
| | | sys.stdout.reconfigure(encoding='utf-8', newline='') |
| | | except Exception: |
| | | pass |
| | | |
| | | path = sys.argv[1] |
| | | if len(sys.argv) < 3: |
| | | print(__doc__) |
| | | sys.exit(1) |
| | | list_only = '--list' in sys.argv |
| | | keep = set() |
| | | if not list_only: |
| | | keep = {int(x) for x in sys.argv[2].split(',') if x.strip() != ''} |
| | | |
| | | lines = open(path, encoding='utf-8', errors='surrogateescape').read().split('\n') |
| | | header, hunks, cur = [], [], None |
| | | for ln in lines: |
| | | if ln.startswith('@@'): |
| | | if cur is not None: |
| | | hunks.append(cur) |
| | | cur = [ln] |
| | | elif cur is None: |
| | | header.append(ln) |
| | | else: |
| | | cur.append(ln) |
| | | if cur is not None: |
| | | hunks.append(cur) |
| | | |
| | | if list_only: |
| | | for i, h in enumerate(hunks): |
| | | print(f' [{i}] {h[0]}') |
| | | sys.exit(0) |
| | | |
| | | out = list(header) |
| | | for i, h in enumerate(hunks): |
| | | if i in keep: |
| | | out.extend(h) |
| | | # ç»å°¾è¡¥åæ¢è¡ |
| | | sys.stdout.write('\n'.join(out)) |