还原现场——前端录制用户行为技术方案

news2024/9/21 22:58:45

一、问题背景

目前,在我们的项目中通常会使用各种各样的埋点和监控来收集页面访问的信息,例如点击埋点、PV埋点等,这些埋点数据能够反应绝大部分的用户行为,但是对于一些关注上下文的使用场景而言这些埋点是不够的。

  • 对于产品而言,通过点击或PV埋点来判断功能的使用情况有时候不够的,通常需要知道用户的真实使用路径以判断用户使用是否与功能设计所预想的保持一致,从而能够更好地分析用户使用情况并进一步优化和推广。

  • 对于开发而言,当我们收到系统异常通知的时候,监控系统只能告诉你系统出现了错误,但是不能给出错误的复现路径,对于稳定复现的错误而言还好,但对于偶发错误或复现路径隐藏较深的场景我们就较难去解决问题。

  • 对于测试而言,用户反馈线上bug的时候,首先需要知道的就是通过什么操作触发了这个问题,有时候用户自己可能也无法二次复现,这样的错误我们也无法通过埋点的数据去推导上下文,因此就会产生较大的沟通成本,在执行测试计划反馈 bug 时同理。

  • ......

图片

因此,我们需要一种手段来获取用户某一时段连续的操作行为,也就是录制用户行为,包括整个会话中的每一个点击、滑动、输入等行为,同时支持回放录制的操作行为,完整且真实地重现用户行为以帮助我们回溯或分析某些使用场景。

二、技术方案

2.1 视频录制

录制用户行为最容易想到的就是将屏幕操作通过视频的方式录制下来,目前浏览器本身已经提供了一套基于音视轨的实时数据流传输方案 WebRTC(Web Real-Time Communications),在我们的录屏使用场景主要关注以下几个 API:

  • getDisplayMedia() - 提示用户给予使用媒体输入的许可从而获取屏幕的流;

  • MediaRecorder() - 生成对指定的媒体流进行录制的 MediaRecorder 对象;

  • ondataavailable - 当 MediaRecorder 将媒体数据传递到应用程序以供使用时将触发该事件;

整体录制流程如下:

  1. 调用mediaDevices.getDisplayMedia()由用户授权选择屏幕进行录制,获取到数据流;

  2. 生成一个new MediaRecorder()对象录制获取的屏幕的数据流;

  3. 在 MediaRecorder 对象上设置ondataavailable监听事件用于获取录制的 Blob 数据。

 

html

复制代码

<template>
<video ref="playerRef"></video>
<button @click="handleStart">开启录制</button>
<button @click="handlePause">暂停录制</button>
<button @click="handleResume">继续录制</button>
<button @click="handleStop">结束录制</button>
<button @click="handleReplay">播放录制</button>
<button @click="handleReset">重置内容</button>
</template>

<script lang="ts" setup>
import { ref, reactive } from 'vue';

const playerRef = ref();
const state = reactive({
mediaRecorder: null as null | MediaRecorder,
blobs: [] as Blob[],
});

// 开始录制
const handleStart = async () => {
const stream = await navigator.mediaDevices.getDisplayMedia();
state.mediaRecorder = new MediaRecorder(stream, {
mimeType: 'video/webm',
});
state.mediaRecorder.addEventListener('dataavailable', (e: BlobEvent) => {
state.blobs.push(e.data);
});
state.mediaRecorder?.start();
};
// canvas录制(特殊处理)
const handleCanvasRecord = () => {
const stream = canvas.captureStream(60); // 60 FPS recording
const recorder = new MediaRecorder(stream, {
mimeType: 'video/webm;codecs=vp9',
});
recorder.ondataavailable = (e) => {
state.blobs.push(e.data);
};
}
// 暂停录制
const handlePause = () => { state.mediaRecorder?.pause() };
// 继续录制
const handleResume = () => { state.mediaRecorder?.resume() };
// 停止录制
const handleStop = () => { state.mediaRecorder?.stop() };
// 播放录制
const handleReplay = () => {
if (state.blobs.length === 0 || !playerRef.value) return;
const blob = new Blob(state.blobs, { type: 'video/webm' });
playerRef.value.src = URL.createObjectURL(blob);
playerRef.value.play();
};

const handleReset = () => {
state.blobs = [];
state.mediaRecorder = null;
playerRef.value.src = null;
};
const handleDownload = () => {
if (state.blobs.length === 0) return;
const blob = new Blob(state.blobs, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.style.display = 'none';
a.download = 'record.webm';
a.click();
};
</script>

图片

尽管浏览器原生提供了这样既简单又实用的屏幕录制解决方案,但在我们实际应用场景中仍旧有非常多的问题:

  1. 由用户感知并控制:通过 WebRTC 提供的 API 所实现的用户行为录制在开始录制前会通过弹窗来让用户完成对所需录制屏幕的授权,所有的录制行为均由用户自主控制,这种让用户感知到系统录制的方式对于我们预期的使用而言是不合适的,我们预期的录制行为对于用户而言应该是无感的,这种技术方案更适用于类似啄木鸟这种反馈系统由用户主动上报问题的场景或者考试系统屏幕监控、在线面试屏幕共享等等。

  2. 录制数据无法脱敏:视频录制过程中直接就将整个页面的内容录制下来,对于一些敏感的数据同样也会直接录制下来,在录制的过程中我们无法进行脱敏,这对于一些数据安全要求比较高或者涉及用户隐私的场景就不适用了。

  3. WebRTC 兼容性:在实现录制过程中使用的几个 WebRTC API 都具有一定的兼容性要求,不同的浏览器的支持情况各不相同,具体可进行相应的兼容性查询。

2.2 页面截图

众所周知,视频是由一帧帧的画面组合而成的,因此我们可以按照一定时间间隔来截图的方式保存当前页面快照,然后将快照按照相同的截取速度播放形成视频就能实现用户行为录制了。最常用的截图方法就是以 html2canvas 库为代表的 canvas 截图,我们在使用过程中也发现了较多问题:

  1. canvas 截图有较多局限之处,例如无法绘制动画、样式错位、不支持部分CSS样式等;

  2. 截图性能开销较大,可能会导致掉帧,例如我们在尝试中 css 动画有非常明显的卡顿等;

  3. 截图资源体积大,我们尝试中截图时单张图片体积为200k左右,以24帧来算一分钟录制的图片体积将近300MB,对带宽和资源存储都是浪费;

  4. 在需要忽略的元素上增加 data-html2canvas-ignore 属性或者设置 ignoreElements 属性删除特定元素可以对某些特定数据或内容进行脱敏,但会直接删除元素无法做到“有占位但无内容”效果,影响页面布局。

 

html

复制代码

<template>
<el-button @click="handleStart">开启录制</el-button>
<el-button @click="handleStop">停止录制</el-button>
<el-button @click="handleReplay">播放录制</el-button>
<img :src="state.imgs[state.num ?? 0]" />
</template>

<script lang="ts" setup>
import { reactive } from 'vue';
import html2canvas from 'html2canvas';

const state = reactive({
visible: false,
imgs: [] as string[],
num: 0,
recordInterval: null as any,
replayInterval: null as any,
});

const FPS = 30;
const interval = 1000 / FPS;
const handleStart = async () => {
handleReset();
state.recordInterval = setInterval(() => {
if (state.imgs.length > 100) {
handleStop();
return;
}
html2canvas(document.body).then((canvas: any) => {
const img = canvas.toDataURL();
state.imgs.push(img);
});
}, interval);
};

const handleStop = () => {
state.recordInterval && clearInterval(state.recordInterval);
};

const handleReplay = async () => {
state.recordInterval && clearInterval(state.recordInterval);
state.num = 0;
state.visible = true;
state.replayInterval = setInterval(() => {
if (state.num >= state.imgs.length - 1) {
clearInterval(state.replayInterval);
return;
}
state.num++;
}, interval);
};

const handleReset = () => {
state.imgs = [];
state.recordInterval = null;
state.replayInterval = null;
state.num = 0;
};
</script>

实际内容截图效果

图片

图片

2.3 Dom 快照录制

每一个瞬间我们看到的页面都是浏览器当前渲染的 DOM节点,那么我们完全可以将 DOM 节点保存下来,并持续记录 DOM 节点的变化,然后再将记录的 DOM 节点数据通过浏览器渲染回放,这样即可实现用户行为录制的需求。整个思路非常简单,但具体实现起来是非常复杂的事情,我们需要考虑 DOM 节点数据如何保存、如何捕获用户行为并记录 DOM 节点变换和如何将记录的数据在浏览器上回放出来等。所幸当前社区已经有非常成熟的库,也就是 rrweb(record and replay the web)🎉

rrweb 主要由 3 部分组成:

  1. rrweb-snapshot,包含 snapshot 和 rebuild 两部分,snapshot 用于将 DOM 及其状态转化为可序列化的数据结构并添加唯一标识,rebuild 是将 snapshot 记录的数据结构重建为对应 DOM。

  2. rrweb,包含 record 和 replay 两个功能,record 用于记录 DOM 中的所有变更,replay 则是将记录的变更按照对应的时间一一重放。

  3. rrweb-player,为 rrweb 提供一套 UI 控件,提供基于 GUI 的暂停、快进、拖拽至任意时间点播放等功能。

图片

录制过程

rrweb 在录制时会首先进行首屏 DOM 快照,遍历整个页面的 DOM Tree 并通过 nodeType 映射转换为 JSON 结构数据。针对不同 nodeType 类型的节点类型的序列化操作具有非常多的细节,如想了解细节可阅读这部分源码。全量快照数据示例如下:

图片

在获取首屏全量快照之后,我们就需要监听各类变动以获取增量的数据,增量改变的数据也需要同步转换为 JSON 数据进行存储。对于增量数据更新,则是通过 mutationObserver 获取 DOM 增量变化,以及通过全局事件监听、事件(属性)代理的方式进行方法(属性)劫持,并将劫持到的增量变化数据存入 JSON 数据中。针对不同类型的变动有这不同的监听处理方式,如想了解细节可阅读这部分源码。

回放过程

回放主要就是将录制过程中的全量快照和增量快照进行重建复原,那么为保证一个安全可靠的环境在回放时我们不应该执行被录制页面中的 JavaScript,在重建快照的过程中通过 script 标签改写为 noscript 标签和 dom 重建在 iframe 中并设置 sandbox 属性等手段来构建安全可靠的沙盒环境。在沙盒环境中,首先需要对首屏 DOM 快照进行重建,遍历 JSON 产物的同时通过自定义 type 映射到不同的节点构建方法以重建首屏 DOM 结构,然后 rrweb 内部则根据不同的增量类型调用不同的函数在页面对增量数据进行展现。同时,播放时通过录制产生的时间戳来保证回放顺序,通过 Node id 作用至指定的 DOM 节点,通过 requestAnimationFrame 保证页面播放流畅度。

rrweb 实现原理可参考以下相关资料以及源码/官方文档:

  • rrweb:打开 web 页面录制与回放的黑盒子 - 知乎

  • 神策数据王磊:如何用 JS 实现页面录制与回放 - 掘金

  • rrweb录屏原理浅析 - 掘金

  • rrweb 实现原理介绍 - 微信公众号

  • rrweb 带你还原问题现场 - 云音乐大前端

 

html

复制代码

<template>
<button @click="handleStart">开启录制</button>
<button @click="handleStop">结束录制</button>
<button @click="handleReplay">播放录制</button>
<div class="replay" ref="replayRef"></div>
</template>

<script lang="ts" setup>
import { reactive, ref } from 'vue';
import * as rrweb from 'rrweb';
import rrwebPlayer from 'rrweb-player';
import 'rrweb-player/dist/style.css';

const replayRef = ref();
const state = reactive({
events: [] as any[],
stopFn: null as any,
});

const handleStart = () => {
state.stopFn = rrweb.record({
emit(event) {
if (state.events.length > 100) {
// 当事件数量大于 100 时停止录制
handleStop();
} else {
state.events.push(event);
}
},
});
ElMessage('开始录制');
};

const handleStop = () => {
state.stopFn?.();
ElMessage('已停止录制');
};

const handleReplay = () => {
new rrwebPlayer({
target: replayRef.value, // 可以自定义 DOM 元素
// 配置项
props: {
events: state.events,
},
});
};
</script>

图片

从效果上来讲,rrweb 录制内容存储了完整的页面结构能够较好地还原页面的整个操作,并且 rrweb 录制无损录制具有较好的清晰度,不像视频录制和页面截图需要考虑分辨率与产物大小的问题,同时也不像 canvas 截图一样对内容和样式有较大的局限性使得部分页面内容无法录制。

从性能上来讲,rrweb 录制传输的内容为 JSON 数据并且只对用户操作内容作增量记录,当页面静默的时候不会有额外数据的记录,相比较视频录制和页面截图而言大大减少了最终产物的体积,减轻了数据传输的压力,同时也提高了录制的性能。

从功能上来讲,rrweb 除了基础的录制回放功能之外,还具有较好的可扩展性和可操作性:

  • rrweb 录制得到的产物是 JSON 数据,还支持将 JSON 数据转成视频,工具rrvideo

  • 其允许通过配置来进行数据脱敏,不录制某些元素或屏蔽某些事件,详见链接

  • 其还提供了存储优化的策略以减少录制的数据量,详见链接

  • 其还支持自定义插件来进行扩展,详见链接

2.4 方案对比

对比内容视频录制页面截图Dom 快照录制
开源库WebRTC 原生支持html2canvasrrweb
用户感知录制有感录制无感录制无感
产物大小相对较小
兼容性详见相关 API 兼容性部分场景内容截图无法显示兼容性相对较好
可操作性强(支持数据脱敏/加密等)
回放清晰度录制时决定,有损录制录制时决定,有损录制高保真

💡 Dom快照录制 - rrweb 库 是目前最为流行的解决方案,一些商业化平台解决方案也都主要基于 rrweb 库来进行录制与回放的功能开发。但是,方案的选择不是绝对的,在不同的使用场景下选择合适的方案才是最重要的哦 (^_-)

三、应用场景

在获取页面的录屏内容之后,这只是第一步,更重要的是我们能够利用这些录屏信息获取到什么信息,分析出什么内容?

#应用场景说明
1产品功能分析产品在功能的上线后仅通过点击或PV埋点来判断使用情况是不够的,更应该关注于一些关键页面/功能的真实使用场景,通过用户行为录制将用户的操作路径记录下来,通过回放种子用户的操作进行分析或利用算法对使用路径进行分析能够更好了解功能设计的情况,并帮助进一步优化。
2用户访谈记录产品在对用户进行访谈时通过整理用户口述记录和回放访谈录音等方式来进行分析和研究,整体访谈的成本较高,信息利用率也较低,而通过用户行为录制记录下来访谈过程中用户真实的操作记录能够更好地帮助产品来回顾访谈用户的操作习惯。
3问题现场复现解决问题第一步就需要复现问题,但有时候问题的复现操作是极其隐蔽的或者由于用户的使用环境等因素很难定位,通过录制用户行为来保存报错时刻的上下文使得我们能够更好地了解用户报错的操作,最大程度减少沟通和内容传递的成本。
4自动化测试用例通常自动化测试用例的编写和维护都由人工手动来完成,成本相对比较高,后期维护也不方便,通过用户行为录制将录制的数据加以转换就可以更加快捷方便地进行测试用例的采集,同时也便于管理。
5其他还有案例复盘、行为监控/监管、业务流程质检...

四、平台方案

Sentry 平台 提供了录制与回放功能用于进行分析,其应用重点在于查看错误或性能问题发生之前、期间和之后的操作情况,其录制与回放功能同样基于 rrweb 库进行开发。

图片

Hotjar 平台提供了录制与回放功能用于进行分析,其除了录制与回放功能外,其提供了页面热力图等分析能力,以帮助用户更好地了解产品的情况,官方也提供了 Demo 可体验 。

图片

其他相关的商业化平台还有LogRocket、FullStory、marker.io等等。

五、总结

目前,用户行为录制已经在各个场景中广泛使用,例如用户调研、产品分析、Bug回溯、自动化测试和行为监控等等。视频录制、页面截图和 Dom 快照录制等技术方案各有优劣,在面对不同的使用场景时要选择合适的技术方案,但总体而言,以 rrweb 库为代表的 Dom 快照录制技术是目前最为广泛使用也最具优势的技术方案,在各种商业化解决方案中也主要采用该技术方案或思路来实现。

参考资料

  • 用户行为录制技术方案 - 掘金

  • 前端录制回放系统初体验 - 掘金

  • 浏览器录制方案的调研和总结 - 知乎

  • 快速入门 WebRTC:屏幕和摄像头的录制、回放、下载 - 掘金

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1132377.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智慧工地云平台源码 人工智能AI+多系统集成+智能预警平台源码

智慧工地云平台源码 人工智能AI多系统集成智能预警平台 智慧工地企业级监管平台融入AIoT、移动互联网和物联网等领先技术&#xff0c;再结合工地“人、机、料、法、环”五大要素&#xff0c;劳务实名制管理、环境监测管理、安全施工管理、质量及能耗管理等智慧化应用&#xff0…

华为云香港云服务器CPU性能测评_1核2G1M带宽S3服务器

华为云香港云服务器99元一年&#xff0c;S3云服务器1核2G1M带宽&#xff0c;40GB高IO系统盘&#xff0c;活动链接&#xff1a;atengyun.com/go/huawei 购买条件为华为云新用户&#xff0c;阿腾云分享华为云香港99元服务器优惠活动入口、详细配置、价格及注意事项&#xff1a; …

【LeetCode:2698. 求一个整数的惩罚数 | 递归】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

Mac 超好用的工具推荐

Arc Arc 是 2022 年 4 月发布的浏览器产品&#xff0c;在介绍 Arc 浏览器之前&#xff0c;让我们来看下以 Chrome、FireFox、Edge、Safari 为代表的的传统浏览器&#xff1a; 难怪《浏览器是怎么工作的》作者 Paul Irish 曾说&#xff0c;尽管 W3C 并未规范浏览器界面&#xf…

Python深度学习实战-基于class类搭建BP神经网络实现分类任务(附源码和实现效果)

实现功能 上篇文章介绍了用Squential搭建BP神经网络&#xff0c;Squential可以搭建出上层输出就是下层输入的顺序神经网络结构&#xff0c;无法搭出一些带有跳连的非顺序网络结构&#xff0c;这个时候我们可以选择类class搭建封装神经网络结构。 第一步&#xff1a;import ten…

手机扫描二维码的测试用例

二维码概述 二维码本身就是一个URL&#xff0c;只是通过QR码的形式把URL和用户身份信息转换成二进制的0和1&#xff0c;二维码中黑色的色素块代表1&#xff0c;白色的色素块代表0&#xff0c;我们通过相机扫码&#xff0c;就获取了二维码中的URL。 ****测试用例罗列&#xff08…

通过IP地址可以做什么

通过IP地址可以做很多事情&#xff0c;因为它是互联网通信的基础之一。本文将探讨IP地址的定义、用途以及一些可能的应用。 IP地址的用途 1. 设备标识&#xff1a;IP地址用于标识互联网上的每个设备&#xff0c;这包括计算机、服务器、路由器、智能手机等。它类似于我们日常生…

美摄AR人像美颜,全新视觉体验

企业越来越重视通过视觉媒体来提升品牌形象和吸引客户。然而&#xff0c;传统的摄影技术往往无法满足企业对于高质量、个性化视觉内容的需求。这时&#xff0c;美摄AR人像美颜解决方案应运而生&#xff0c;它以其独特的技术和优势&#xff0c;为企业带来了全新的视觉体验。 美…

解读意大利葡萄酒分类系统

由于该国众多的产区和复杂的品种&#xff0c;要想真正掌握意大利葡萄酒是相当困难的。仅仅是试图从复杂混乱的葡萄酒标签中辨别信息的想法就足以让许多人焦虑不安。 位于托斯卡纳的基安蒂酒地区&#xff0c;Il Ciliegio生产的葡萄酒标签上包含以下名称之一:基安蒂酒科利塞内西…

2-Java进阶知识总结-3-集合

文章目录 补充知识-泛型版本信息泛型类常见泛型标识符泛型应用实例注意事项 泛型方法非静态的泛型方法静态的泛型方法&#xff1a;必须声明出自己独立的泛型 泛型接口泛型通配符 补充知识-树基本概念二叉树-普通二叉树二叉树-二叉查找树添加节点流程优点、不足 二叉树-平衡二叉…

了解松散类型

目录 松散类型带来的优势 灵活性和便利性 快速原型开发 动态类型 松散类型的注意事项 类型转换 隐式类型转换 如何正确使用松散类型&#xff1f; 动态类型 便捷的类型转换 灵活性与易用性 潜在的隐式类型转换 避免混淆和错误 当谈到JavaScript编程语言时&#xff…

app拉新平台推广渠道,地推网推接单神器

地推或网推不知道在哪里接单&#xff1f;信息差太大导致价格参差不齐&#xff0c;聚量推客人人高价置顶 聚量推客自己本身是服务商直营平台 相对来说数据更好&#xff0c;我们也拿到了平台首码&#xff1a;000000 填这个就行&#xff0c;属于官方渠道 平台最新上架的产品有&a…

Java即时通讯源码 IM即时通讯系统源码

Java即时通讯源码 IM即时通讯系统源码 基本功能说明及介绍&#xff1a; 客户端&#xff1a;安卓&#xff0c;苹果&#xff0c;&#xff08;可赠送web&#xff0c;pc&#xff09; 开发语言&#xff1a; Java OC C# 运行软件&#xff1a;eclipse Java xcode 数据库&#xff…

选择工业交换机时,需要关注哪些方面的性能?

在工业自动化、能源、交通等领域的网络通信中&#xff0c;工业交换机是一种非常重要的网络设备。它的性能和可靠性直接影响到整个网络的稳定性和安全性。因此&#xff0c;在选择工业交换机时&#xff0c;我们需要关注以下几个方面的性能&#xff1a; 1. 抗干扰性能&#xff1a;…

linux下安装 Chrome 和 chromedriver 以及 selenium webdriver 使用

1 安装 Chrome yum install https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm2 下载 chromedriver # 进入下载目录 cd soft/crawler_tools# 查看chrome 版本号 google-chrome --version# 在chromedriver下载地址中找到对应版本&#xff0c;下载对…

音频类型识别方案-audioset_tagging

audioset_tagging github上开源的音频识别模型&#xff0c;可以识别音频文件的类型并打分给出标签占比&#xff0c;如图 echo off set CHECKPOINT_PATH"module/Cnn14_mAP0.431.pth" set MODEL_TYPE"Cnn14" set CUDA_VISIBLE_DEVICES0 python pytorch\in…

谈谈多用户商城系统的优缺点

多用户商城系统是一种基于互联网的电子商务平台&#xff0c;可以给多个商家提供在线销售、交易和管理的功能。这种系统的出现&#xff0c;为商家和消费者之间的交流和交易提供了更加便捷和高效的方式。那么&#xff0c;多用户商城系统的优缺点有哪些呢&#xff1f; 一、多用户商…

实在智能受邀参加第14届珠中江数字化应用大会,AI赋能智能制造,共话“湾区经验”

制造业是实体经济的主体&#xff0c;是技术创新的主战场&#xff0c;是供给侧结构性改革的重要领域。抢占新一轮产业竞争制高点&#xff0c;制造业的数字化转型已成为行业升级的必由之路。 10月21日&#xff0c;第14届“珠中江”&#xff08;珠海、中山、江门&#xff09;数字…

Amazon商品详情API接口(标题|主图|SKU|价格|库存)

亚马逊商品详情API接口是亚马逊平台提供的API接口&#xff0c;可以通过程序调用API来获取亚马逊商品的相关数据&#xff0c;包括商品价格、库存、评价等信息。这些信息可以帮助开发者和商家更好地了解商品详情&#xff0c;优化用户体验&#xff0c;支持购买决策&#xff0c;竞品…

软件兼容性测试对软件产品起到什么作用?CMA、CNAS软件测评中心分享

软件兼容性测试是指检查软件之间能否正确地进行交互和共享信息。随着用户对来自各种类型软件之间共享数据能力和充分利用空间同时执行多个程序能力的要求&#xff0c;测试软件之间能否协作变得越来越重要。软件兼容性测试工作的目标是保证软件按照用户期望的方式进行交互。 1、…