我的前端相关debugger配置

431 阅读1分钟

全部的debugger配置json地址

chrome 调试

element-debugger.png

过滤控制台信息

console.png

添加 ignore list(这样在 chrome 上断点就不会跳到源码了)

  - ._\/node_modules\/._
  - ._\/.umi\/._
  - ^webpack://.\\\\\\\\\\\\*/react refresh\$
  - /umi\.js\$
  - /react_devtools_backend.js\.js\$
  - ^chrome-extension://fmkadmapgofadopljbjfkapdkoienihi\b.\\\\\\\\\*/react_devtools_backend\.js\$

ignore1.png ignore2.png

其他断点

  • 条件断点一般用于 循环时,当满足某个条件时,才会触发断点
  • 日志断点 一般用于 打印日志,当触发日志断点时,会打印出日志信息
  • 不触发断点一般用于 网站防止操作无限循环 debugger 时,设置后,不会触发断点
  • xhr 断点 可以用来调试 ajax 请求
  • 事件断点 可以用来调试事件

other-debugger.png

代码段

snnippets.png

快速定位代码

css-find-code.png

xhr-find-code.png

xhr 调试

search.png

Performance 性能分析

如页面卡顿找不到原因时 可以用过这个来快速找到源头

Performance.png

Layers

  • 用来测试滚动条 等功能时 测试层级

layers.png

animations

  • 测试动画
  • 在生产环境遇到异常动画导致的问题 可以定位到元素然后样式覆盖

animations.png

dns

  • chrome://net-internals/#dns

截屏2024-03-18 22.39.32.png

node-debugger

  • chrome://inspect/#devices

截屏2024-03-18 22.40.00.png

vue2

  • 使用 vue cli 创建的项目
  • yarn serve
  • ./vscode/launch.json
{
  // 使用 IntelliSense 了解相关属性。
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "chrome",
      "request": "launch",
      "name": "针对 localhost 启动 Chrome",
      "url": "http://localhost:8080",
      "runtimeArgs": [
        "--auto-open-devtools-for-tabs" // 自动打开开发者工具
      ],
      "webRoot": "${workspaceFolder}",
      "sourceMapPathOverrides": {
        "webpack://项目名字/src/*": "${workspaceFolder}/src/*"
      }
    }
  ]
}
  • vue.config.js
const { defineConfig } = require('@vue/cli-service');
module.exports = defineConfig({
  transpileDependencies: true,
  configureWebpack(config) {
    config.devtool = 'source-map';
  },
});

vue2.png

vue3

  • pnpm create vue@latest
  • yarn serve
  • ./vscode/launch.json
{
  // 使用 IntelliSense 了解相关属性。
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "chrome",
      "request": "launch",
      "name": "针对 localhost 启动 Chrome",
      "runtimeArgs": ["--auto-open-devtools-for-tabs"],
      "url": "http://localhost:5173",
      "webRoot": "${workspaceFolder}"
    }
  ]
}

vue3.png

  • 以 umi4 为例
  • yarn serve
  • ./vscode/launch.json
{
  // 使用 IntelliSense 了解相关属性。
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "chrome",
      "request": "launch",
      "name": "Launch Chrome against localhost",
      "url": "http://localhost:8000",
      "webRoot": "${workspaceFolder}",
      "skipFiles": [
        "${workspaceFolder}/<node_internals>/**",
        "${workspaceFolder}/node_modules/**",
        "${workspaceFolder}/webpack/**",
        "${workspaceFolder}/.umi/**",
        "${workspaceFolder}/umi\\.js$",
        "${workspaceFolder}/@@/devScripts.js$",
        "${workspaceFolder}/^webpack://.*/react refresh$",
        "${workspaceFolder}/react_devtools_backend.js\\.js$"
      ],
      "smartStep": true,
      "sourceMaps": true
    }
  ]
}

umi.png

FAQ

debugger 行数不对

  chainWebpack: function(config) {
    config.devtool('source-map');
  }

ts-node

  • ./vscode/launch.json
{
  // 使用 IntelliSense 了解相关属性。
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "ts-node",
      "type": "node",
      "request": "launch",
      "runtimeExecutable": "node",
      "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
      "args": ["scripts/generateBizConfig.ts"],
      "cwd": "${workspaceRoot}",
      "internalConsoleOptions": "openOnSessionStart",
      "skipFiles": ["<node_internals>/**", "node_modules/**"]
    }
  ]
}

ts-node.png

terminal 终端调试

terminal.png

puppeteer UI 自动化测试

utils.tsx

const fs = require('fs');

/**
 * @param {*} path
 */
async function removeDir(path) {
  if (fs.existsSync(path)) {
    const dirs = [];
    const files = fs.readFileSync(path);
    files.forEach(async (file) => {
      const childPath = `${path}/${file}`;
      if (fs.statSync(childPath).isDirectory()) {
        await removeDir(childPath);
        dirs.push(childPath);
      } else {
        fs.unlinkSync(path);
      }
    });
    dirs.forEach((fir) => fs.rmdirSync(fir));
  } else {
    console.log('no such file or directory, failed to remove');
  }
}

async function goto(page, link) {
  return page.evaluate((link) => {
    location.href = link;
  }, link);
}

async function click(page, selector, timeout = 30000) {
  await page.waitForSelector(selector, { visible: true, timeout });
  let error;
  while (timeout > 0) {
    try {
      await page.click(selector);
      return;
    } catch (e) {
      await page.waitFor(100);
      timeout -= 100;
      error = e;
    }
  }
  throw err;
}

module.exports = {
  removeDir,
  goto,
  click,
};

testConfig.js

module.exports = {
  domain: 'http://localhost:8000',
  username: 'admin',
  password: 'admin',
};

index.js

const puppeteer = require('puppeteer');
const path = require('path');
const testConfig = require('./testConfig');
const CommentTable = require('./commonTable/class');
const utils = require('./utils');

(async () => {
  const browser = await puppeteer.launch({
    // 有浏览器界面启动
    headless: false,
    // 放慢浏览器执行速度 方便测试观察
    slowMo: 100,
    devtools: true,
    args: ['--window-size=1480,960', '--unlimited-storage'],
    debuggerPort: 9992,
  });

  const startTime = new Date().getTime();
  const page = await browser.newPage();
  await page.setViewport({
    width: 1480,
    height: 960,
  });

  await utils.goto(page, `${testConfig.domain}/login`);
  await page.waitForNavigation();

  const username = await page.$("input[type='text']");
  await username.type(testConfig.username);
  const password = await page.$("input[type='password']");
  await password.type(testConfig.password);
  const submit = await page.$("button[type='submit']");
  await Promise.all([submit.click(), page.waitForNavigation()]);
  const submitBtn = await page.waitForSelector('.ant-tour-close');
  await submitBtn.click();

  page.on('pageerror', (err) => {
    console.error(err);
  });

  // await utils.removeDir(path.join(__dirname, '/screenshot'));

  let testResList = [];
  let isLastTest = false;

  function handleLastTest(flag) {
    isLastTest = flag;
    return statisticsTestResult;
  }

  function statisticsTestResult(testResult) {
    testResList.push(testResult);
    if (isLastTest) {
      let allCount = 0;
      let passCount = 0;
      testResList.forEach((item) => {
        allCount += item.allCount;
        passCount += item.passCount;
      });

      testResList.push({
        moduleName: '总计',
        allCount,
        passCount,
      });

      console.log('测试结果完成, 统计结果如下:');
      console.table(testResList);

      if (allCount === passCount && passCount > 0) {
        console.log('\x1B[32m%s\x1B[39m', '测试通过');
      }

      const spendTime = (new Date().getTime() - startTime) / 1000;
      console.log('\x1B[32m%s\x1B[39m', `done in ${spendTime}s.`);
    }
  }

  // 非最后的测试用例  handleLastTest(false)  最后的测试用例 handleLastTest(true)
  await CommentTable.CommentTableAddTest(page, handleLastTest(true));

  // await page.close();
  // await browser.close();
})();

commonTable/class.js

const testConfig = require('../testConfig');
const utils = require('../utils');
const { domain } = testConfig;

async function CommentTableAddTest(page, callback) {
  let allCount = 0;
  let passCount = 0;
  const moduleName = '公众组件class';
  const imgId = Date.now();
  try {
    allCount++;
    await utils.goto(page, `${testConfig.domain}/home/class`, {
      timeout: 5 * 1000,
      waitUntil: [
        'domcontentloaded', // 等待 DOMContentLoaded 事件触发
      ],
    });
    // 等待列表接口请求完成
    await page.waitForRequest(`${domain}/getActivityList?page=1&limit=30&aaaaaa=1&my=121213`);
    const EditBtnElement = '.ant-table-tbody .ant-table-row td:last-child >div >button';
    const SubmitBtn = '.ant-modal-content > div.ant-modal-footer > button.ant-btn.ant-btn-primary';
    const SelectElements = '.ant-modal-content .ant-select';

    await utils.click(page, EditBtnElement);

    const otherFormItemInput = await page.waitForSelector('#otherFormItem', {
      timeout: 2000,
    });
    const keywords = '测试';
    await otherFormItemInput.type(keywords, { delay: 100 });

    const typeSelect = await page.$$(SelectElements);
    await typeSelect[0].click();
    await handleDropdownSelect(page, 1);

    await utils.click(page, SubmitBtn);
    page.on('response', (response) => {
      response
        .json()
        .then((data) => {
          const resCode = Number(data.code);
          const apiUrl = response.url();
          const checkApiPass = apiUrl === `${domain}/updateActivityList`;
          if (resCode !== 200) {
            console.error({
              msg: data.msg,
              code: data.code,
              title: '测试不通过',
              module: moduleName,
            });
            page.screenshot({
              path: `./test/screenshot/${moduleName}${imgId}.png`,
            });
          } else if (resCode === 200) {
            if (checkApiPass) passCount++;
          }
          if (checkApiPass) {
            callback({
              allCount,
              passCount,
              moduleName,
            });
          }
        })
        .catch((err) => {
          console.error('err', err);
          callback({
            allCount,
            passCount,
            moduleName,
          });
          page.screenshot({
            path: `./test/screenshot/${moduleName}${imgId}.png`,
          });
        });
    });

    callback({
      allCount,
      passCount,
      moduleName,
    });
    page.screenshot({
      path: `./test/screenshot/${moduleName}${imgId}.png`,
    });
  } catch (error) {
    console.error('error', error);
  }
}

async function handleDropdownSelect(page, selectedIndex) {
  const allDownList = await page.$$('.ant-select-dropdown');
  const optionsList = await allDownList[allDownList.length - 1].$$('.ant-select-item');
  const selectedItem = optionsList[selectedIndex];
  await selectedItem.click();
}

module.exports.CommentTableAddTest = CommentTableAddTest;

package.json

{
  "scripts": {
    "autotest": "node test"
  },
  "dependencies": {
    "puppeteer": "^19.7.3"
  }
}

puppeteer-debugger.png

node

新建一个目录

mkdir debug
cd debug
npm init -y
  • 添加 index.js
// index.js
const os = require('os');
const homedir = os.homedir();
console.log(homedir);

使用 chrome 调试

  • node --inspect-brk index.js
  • 在 chrome 浏览器打开 chrome://inspect/#devices

chrome-inspect.png

vscode

  • ./vscode/launch.json
{
  // 使用 IntelliSense 了解相关属性。
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "首行断点",
      "type": "node",
      "request": "launch",
      // 首行断点
      // "stopOnEntry": true,
      "skipFiles": ["<node_internals>/**"],
      "program": "${workspaceFolder}/index.js"
    }
  ]
}

node-vscode-debugger.png

next

pnpm dev

/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --remote-debugging-port=9222
{
  // 使用 IntelliSense 了解相关属性。
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "后端",
      "type": "node-terminal",
      "request": "launch",
      "command": "npm run dev"
    },
    {
      "name": "前端",
      "type": "chrome",
      "request": "attach",
      "port": 9222,
      "userDataDir": false,
      "runtimeExecutable": "canary",
      "sourceMaps": true,
      "sourceMapPathOverrides": {
        "meteor://💻app/*": "${workspaceFolder}/*",
        "webpack:///./~/*": "${workspaceFolder}/node_modules/*",
        "webpack://?:*/*": "${workspaceFolder}/*"
      },
      "runtimeArgs": [
        // 无痕模式
        // "--incognito",
        // 自动打开开发者工具
        "--auto-open-devtools-for-tabs",
        "--user-data-dir=${workspaceFolder}/.vscode/chrome"
      ],
      "skipFiles": ["${workspaceFolder}/node_modules/**/*.js", "<node_internals>/**/*.js"]
    }
  ]
}

next-terminal.png next-client.png

使用 chrome 调试

  • pnpm start:debug

nest-inspect.png

使用 vscode 调试

1.nest start --debug --watch

  • npm run start:debug
  • ./vscode/launch.json
{
  // 使用 IntelliSense 了解相关属性。
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "nest start --debug",
      "port": 9229,
      "request": "attach",
      "skipFiles": ["<node_internals>/**"],
      "type": "node",
      "console": "integratedTerminal"
    }
  ]
}

nest-start-debug.png

2. npm script

  • 不需要自己自动项目了
  • ./vscode/launch.json
{
  // 使用 IntelliSense 了解相关属性。
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "npm script",
      "request": "launch",
      "runtimeArgs": ["start", "dev"],
      // 首行断点
      // "stopOnEntry": true,
      "runtimeExecutable": "npm",
      "skipFiles": ["<node_internals>/**"],
      "type": "node",
      "console": "integratedTerminal"
    }
  ]
}

3. debugger 前端静态资源

  • pnpm start:dev
{
  // 使用 IntelliSense 了解相关属性。
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "chrome",
      "request": "launch",
      "name": "static",
      "url": "http://localhost:3000",
      "webRoot": "${workspaceRoot}/src/public"
    }
  ]
}

nest-static-html.png

4. 使用 canary 调试前端静态资源

  • 可以保留浏览器的配置 拓展程序等等
  • 电脑根目录执行
/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --remote-debugging-port=9222
{
  "name": "canary-前端",
  "type": "chrome",
  "request": "attach",
  "port": 9222,
  "userDataDir": false,
  "runtimeExecutable": "canary",
  "sourceMaps": true,
  "sourceMapPathOverrides": {
    "meteor://💻app/*": "${workspaceFolder}/*",
    "webpack:///./~/*": "${workspaceFolder}/node_modules/*",
    "webpack://?:*/*": "${workspaceFolder}/*"
  },
  "runtimeArgs": [
    // 无痕模式
    // "--incognito",
    // 自动打开开发者工具
    "--auto-open-devtools-for-tabs",
    "--user-data-dir=${workspaceFolder}/.vscode/chrome"
  ],
  "skipFiles": ["${workspaceFolder}/node_modules/**/*.js", "<node_internals>/**/*.js"],
  "webRoot": "${workspaceRoot}/src/public"
}

nest-canary.png

jest

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "jest",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/jest/bin/jest.js",
      "console": "integratedTerminal",
      "args": [
        // 不再用 worker 进程并行跑测试用例,而是在当前进程串行跑:
        "-i"
      ]
    }
  ]
}
  • 只调试某个测试用例
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "jest",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/jest/bin/jest.js",
      "console": "integratedTerminal",
      "args": [
        // 不再用 worker 进程并行跑测试用例,而是在当前进程串行跑:
        "-i",
        "packages/ims-view-pc/tests/index.test.tsx",
        "-t",
        "CommonDemo"
      ]
    }
  ]
}

调试当前打开的文件

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "jest",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/jest/bin/jest.js",
      "console": "integratedTerminal",
      "args": [
        // 不再用 worker 进程并行跑测试用例,而是在当前进程串行跑:
        "-i",
        "${file}"
      ]
    }
  ]
}
  • 手动输入名称调试
    • (支持正则 例如输入 CommonDemo\d)
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "jest",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/jest/bin/jest.js",
      "console": "integratedTerminal",
      "args": [
        // 不再用 worker 进程并行跑测试用例,而是在当前进程串行跑:
        "-i",
        "${file}",
        "-t",
        "${input:case}"
      ]
    }
  ],
  "inputs": [
    {
      "type": "promptString",
      "id": "case",
      "description": "CommonDemo",
      "default": ""
    }
  ]
}

jest-debugger.png

FAQ

发生异常: Error: Cannot find module 'jest-environment-jest-environment-jsdom/package.json' from 'xxx'

  • 关闭异常断点等

canary

  • 前端调试使用 canary 浏览器 可以保留浏览器的配置 拓展程序等
  • 以 umi 为例
/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --remote-debugging-port=9222
  • pnpm run start 启动项目
  • ./vscode/launch.json
    {
      "name": "针对 localhost 启动 Chrome",
      "type": "chrome",
      "request": "attach",
      "port": 9222,
      "userDataDir": false,
      "runtimeExecutable": "canary",
      "sourceMaps": true,
      "sourceMapPathOverrides": {
        "meteor://💻app/*": "${workspaceFolder}/*",
        "webpack:///./~/*": "${workspaceFolder}/node_modules/*",
        "webpack://?:*/*": "${workspaceFolder}/*"
      },
      "runtimeArgs": [
        // 无痕模式
        // "--incognito",
        // 自动打开开发者工具
        "--auto-open-devtools-for-tabs",
        "--user-data-dir=${workspaceFolder}/.vscode/chrome"
      ],
      "skipFiles": [
    "${workspaceFolder}/node_modules/**/*.js",
    "<node_internals>/**/*.js"
  ]
    }
    ```

canary-umi.png

参考地址