未验证 提交 4c2dadbe 编写于 作者: O openharmony_ci 提交者: Gitee

!8322 【OpenHarmony 4.0.5.5】【master】【ARKUI子系统】【TOD】【rk3568】【必现】新增web-xts用例

Merge pull request !8322 from yupeng/master
......@@ -29,6 +29,7 @@ group("arkui") {
"ace_ets_test:ActsAceEtsTest",
"ace_ets_third_test:ActsAceEtsThirdTest",
"ace_ets_web_dev:ActsAceWebDevTest",
"ace_ets_web_dev_four:ActsAceWebDevFourTest",
"ace_ets_web_dev_three:ActsAceWebDevThreeTest",
"ace_ets_web_dev_two:ActsAceWebDevTwoTest",
"ace_ets_xcomponent:ActsAceXComponentEtsTest",
......
{
"app": {
"bundleName": "com.open.harmony.acewebfourtest",
"vendor": "huawei",
"versionCode": 1000000,
"versionName": "1.0.0",
"debug": false,
"icon": "$media:icon",
"label": "$string:app_name",
"description": "$string:description_application",
"distributedNotificationEnabled": true,
"keepAlive": true,
"singleUser": true,
"minAPIVersion": 10,
"targetAPIVersion": 10,
"car": {
"apiCompatibleVersion": 10,
"singleUser": false
}
}
}
\ No newline at end of file
{
"string":[
{
"name":"app_name",
"value":"ohosProject"
}
]
}
\ No newline at end of file
# Copyright (c) 2021 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import("//test/xts/tools/build/suite.gni")
ohos_js_hap_suite("ActsAceWebDevFourTest") {
hap_profile = "entry/src/main/module.json"
js_build_mode = "debug"
deps = [
":ace_ets_web_dev_js_assets",
":ace_ets_web_dev_resources",
]
ets2abc = true
certificate_profile = "signature/openharmony_sx.p7b"
hap_name = "ActsAceWebDevFourTest"
}
ohos_app_scope("ace_ets_web_dev_app_profile") {
app_profile = "AppScope/app.json"
sources = [ "AppScope/resources" ]
}
ohos_js_assets("ace_ets_web_dev_js_assets") {
source_dir = "entry/src/main/ets"
}
ohos_resources("ace_ets_web_dev_resources") {
sources = [ "entry/src/main/resources" ]
deps = [ ":ace_ets_web_dev_app_profile" ]
hap_profile = "entry/src/main/module.json"
}
{
"description": "Configuration for hjunit demo Tests",
"driver": {
"type": "OHJSUnitTest",
"test-timeout": "180000",
"bundle-name": "com.open.harmony.acewebfourtest",
"module-name": "phone",
"shell-timeout": "600000",
"testcase-timeout": 70000
},
"kits": [{
"test-file-name": [
"ActsAceWebDevFourTest.hap"
],
"type": "AppInstallKit",
"cleanup-apps": true
},
{
"type": "ShellKit",
"run-command": [
"power-shell wakeup",
"power-shell setmode 602"
]
}
]
}
\ No newline at end of file
import AbilityStage from "@ohos.app.ability.AbilityStage"
export default class MyAbilityStage extends AbilityStage {
onCreate() {
console.log("[Demo] MyAbilityStage onCreate")
globalThis.stageOnCreateRun = 1;
globalThis.stageContext = this.context;
}
}
import Ability from '@ohos.app.ability.UIAbility'
export default class MainAbility extends Ability {
onCreate(want,launchParam){
// Ability is creating, initialize resources for this ability
console.log("[Demo] MainAbility onCreate")
globalThis.abilityWant = want;
}
onDestroy() {
// Ability is destroying, release resources for this ability
console.log("[Demo] MainAbility onDestroy")
}
onWindowStageCreate(windowStage) {
// Main window is created, set main page for this ability
console.log("[Demo] MainAbility onWindowStageCreate windowStage="+ windowStage)
globalThis.windowStage = windowStage
globalThis.abilityContext = this.context
windowStage.setUIContent(this.context, "MainAbility/pages/web", null)
}
onWindowStageDestroy() {
//Main window is destroyed, release UI related resources
console.log("[Demo] MainAbility onWindowStageDestroy")
}
onForeground() {
// Ability has brought to foreground
console.log("[Demo] MainAbility onForeground")
}
onBackground() {
// Ability has back to background
console.log("[Demo] MainAbility onBackground")
}
};
/*
* Copyright (c) 2021~2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import events_emitter from '@ohos.events.emitter';
import AbilityDelegatorRegistry from '@ohos.application.abilityDelegatorRegistry';
import { Hypium } from '@ohos/hypium';
import testsuite from '../../test/List.test';
import Utils from '../../test/Utils';
import web_webview from '@ohos.web.webview';
import fileio from '@ohos.fileio';
import prompt from '@system.prompt';
let loadedUrl;
@Entry
@Component
struct Index {
controller: web_webview.WebviewController = new web_webview.WebviewController();
@State outputStr: string = ''
@State playing: boolean = false
@State str:string="emitOnLoadIntercept"
onPageShow(){
let valueChangeEvent={
eventId:10,
priority:events_emitter.EventPriority.LOW
};
events_emitter.on(valueChangeEvent,this.valueChangeCallBack);
}
private valueChangeCallBack=(eventData)=>{
console.info("web page valueChangeCallBack");
if(eventData != null){
console.info("valueChangeCallBack:"+ JSON.stringify(eventData));
if(eventData.data.ACTION != null){
this.str = eventData.data.ACTION;
}
}
}
aboutToAppear(){
let abilityDelegator: any
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator()
let abilityDelegatorArguments: any
abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments()
console.info('start run testcase!!!')
Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite)
}
build(){
Column(){
Row(){
Button("web click").key('webcomponent').onClick(async ()=>{
console.info("key==>"+this.str)
switch(this.str){
case "emitOnLoadIntercept":{
this.controller.loadUrl("https://www.baidu.com/")
break;
}
default:
console.info("can not match case")
}
})
}
Web({src:"www.example.com",controller:this.controller})
.onLoadIntercept((event) => {
console.log('url:' + event.data.getRequestUrl())
Utils.emitEvent(event.data.getRequestUrl(),2)
console.log('isMainFrame:' + event.data.isMainFrame())
console.log('isRedirect:' + event.data.isRedirect())
console.log('isRequestGesture:' + event.data.isRequestGesture())
return false
})
.onErrorReceive((event) => {
console.log('getErrorInfo:' + event.error.getErrorInfo())
Utils.emitEvent(event.error.getErrorInfo(),3)
})
}
}
}
\ No newline at end of file
/*
* Copyright (c) 2021 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import events_emitter from '@ohos.events.emitter';
import Utils from '../../test/Utils';
import web_webview from '@ohos.web.webview';
function Uint8ArrayToString(dataArray) {
var dataString = ''
for (var i = 0; i < dataArray.length; i++) {
dataString += String.fromCharCode(dataArray[i])
}
return dataString
}
function ParseX509CertInfo(x509CertArray) {
let res: string = 'getCertificate success: len = ' + x509CertArray.length;
for (let i = 0; i < x509CertArray.length; i++) {
res += ', index = ' + i + ', issuer name = '
+ Uint8ArrayToString(x509CertArray[i].getIssuerName().data) + ', subject name = '
+ Uint8ArrayToString(x509CertArray[i].getSubjectName().data) + ', valid start = '
+ x509CertArray[i].getNotBeforeTime()
+ ', valid end = ' + x509CertArray[i].getNotAfterTime()
}
return res
}
@Entry
@Component
struct Second {
@State outputStr: string = ''
webviewCtl: web_webview.WebviewController = new web_webview.WebviewController();
@State str:string="emitGetCertificate"
onPageShow(){
let valueChangeEvent={
eventId:10,
priority:events_emitter.EventPriority.LOW
};
events_emitter.on(valueChangeEvent,this.valueChangeCallBack);
}
private valueChangeCallBack=(eventData)=>{
console.info("web page valueChangeCallBack");
if(eventData != null){
console.info("valueChangeCallBack:"+ JSON.stringify(eventData));
if(eventData.data.ACTION != null){
this.str = eventData.data.ACTION;
}
}
}
build(){
Column(){
Row(){
Button("web click").key('webcomponenttwo').onClick(async ()=>{
console.info("key==>"+this.str);
switch(this.str){
case "emitGetCertificate":{
this.webviewCtl.loadUrl('https://www.baidu.com')
try {
this.webviewCtl.getCertificate().then(x509CertArray => {
this.outputStr = ParseX509CertInfo(x509CertArray);
console.info('11111111111111'+this.outputStr)
Utils.emitEvent(this.outputStr,4)
})
} catch (error) {
this.outputStr = 'getCertificate failed: ' + error.code + ", errMsg: " + error.message;
}
break;
}
default:
console.info("can not match case");
}
})
}
Web({ src: 'https://www.example.com', controller: this.webviewCtl })
.fileAccess(true)
.javaScriptAccess(true)
.domStorageAccess(true)
.onlineImageAccess(true)
.onPageEnd((e) => {
this.outputStr = 'onPageEnd : url = ' + e.url
})
}
}
}
\ No newline at end of file
/*
* Copyright (c) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import TestRunner from '@ohos.application.testRunner'
import AbilityDelegatorRegistry from '@ohos.application.abilityDelegatorRegistry'
var abilityDelegator = undefined
var abilityDelegatorArguments = undefined
function translateParamsToString(parameters) {
const keySet = new Set([
'-s class', '-s notClass', '-s suite', '-s it',
'-s level', '-s testType', '-s size', '-s timeout',
'-s dryRun'
])
let targetParams = '';
for (const key in parameters) {
if (keySet.has(key)) {
targetParams = `${targetParams} ${key} ${parameters[key]}`
}
}
return targetParams.trim()
}
async function onAbilityCreateCallback() {
console.log("onAbilityCreateCallback");
}
async function addAbilityMonitorCallback(err: any) {
console.info("addAbilityMonitorCallback : " + JSON.stringify(err))
}
export default class OpenHarmonyTestRunner implements TestRunner {
constructor() {
}
onPrepare() {
console.info("OpenHarmonyTestRunner OnPrepare ")
}
async onRun() {
console.log('OpenHarmonyTestRunner onRun run')
abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments()
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator()
var testAbilityName = abilityDelegatorArguments.bundleName + '.MainAbility'
let lMonitor = {
abilityName: testAbilityName,
onAbilityCreate: onAbilityCreateCallback,
};
abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback)
var cmd = 'aa start -d 0 -a com.example.myapplication.MainAbility' + ' -b ' + abilityDelegatorArguments.bundleName
cmd += ' '+translateParamsToString(abilityDelegatorArguments.parameters)
var debug = abilityDelegatorArguments.parameters["-D"]
if (debug == 'true')
{
cmd += ' -D'
}
console.info('cmd : '+cmd)
abilityDelegator.executeShellCommand(cmd,
(err: any, d: any) => {
console.info('executeShellCommand : err : ' + JSON.stringify(err));
console.info('executeShellCommand : data : ' + d.stdResult);
console.info('executeShellCommand : data : ' + d.exitCode);
})
console.info('OpenHarmonyTestRunner onRun end')
}
};
\ No newline at end of file
/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import webJsunit from './WebJsunit.test'
import webTwoJsunit from './WebTwoJsunit.test'
export default function testsuite() {
webJsunit()
webTwoJsunit()
}
\ No newline at end of file
/*
* Copyright (c) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import events_emitter from '@ohos.events.emitter';
import { expect } from "@ohos/hypium";
export default class Utils {
static sleep(time){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("ok")
},time)
}).then(()=>{
console.info(`sleep ${time} over...`)
})
}
static registerEvent(testCaseName,expectedValue,eventId,done){
console.info(`[${testCaseName}] START`);
try{
let callBack=(backData)=>{
try{
console.info(`${testCaseName} get result is:`+JSON.stringify(backData));
expect(backData.data.ACTION).assertEqual(expectedValue);
console.info(`[${testCaseName}] END`);
}catch(err){
console.info(`[${testCaseName}] err:`+JSON.stringify(err));
}
done()
}
let innerEvent = {
eventId:eventId,
priority:events_emitter.EventPriority.LOW
}
events_emitter.on(innerEvent,callBack)
}catch(err){
console.info(`[${testCaseName}] err:`+JSON.stringify(err));
}
}
static emitEvent(actualValue,eventId){
try {
let backData = {
data: {
"ACTION": actualValue
}
}
let backEvent = {
eventId:eventId,
priority:events_emitter.EventPriority.LOW
}
console.info("webFlag start to emit action state");
events_emitter.emit(backEvent, backData);
} catch (err) {
console.info("webFlag emit action state err: " + JSON.stringify(err));
}
}
static registerEventTwo(testCaseName,eventId,done){
console.info(`[${testCaseName}] START`);
try{
let callBack=(backData)=>{
try{
console.info(`${testCaseName} get result is:`+JSON.stringify(backData));
expect(backData.data.actualValue).assertLarger(backData.data.expectedValue-100);
expect(backData.data.actualValue).assertLess(backData.data.expectedValue-(-100));
console.info(`[${testCaseName}] END`);
}catch(err){
console.info(`[${testCaseName}] err:`+JSON.stringify(err));
}
done()
}
let innerEvent = {
eventId:eventId,
priority:events_emitter.EventPriority.LOW
}
events_emitter.on(innerEvent,callBack)
}catch(err){
console.info(`[${testCaseName}] err:`+JSON.stringify(err));
}
}
static emitEventTwo(expectedValue,actualValue,eventId){
try {
let backData = {
data: {
"expectedValue":expectedValue,
"actualValue":actualValue
}
}
let backEvent = {
eventId:eventId,
priority:events_emitter.EventPriority.LOW
}
console.info("webFlag start to emit action state");
events_emitter.emit(backEvent, backData);
} catch (err) {
console.info("webFlag emit action state err: " + JSON.stringify(err));
}
}
static registerContainEvent(testCaseName,expectedValue,eventId,done){
console.info(`[${testCaseName}] START`);
try{
let callBack=(backData)=>{
try{
console.info(`${testCaseName} get result is:`+JSON.stringify(backData));
expect(backData.data.ACTION).assertContain(expectedValue);
console.info(`[${testCaseName}] END`);
}catch(err){
console.info(`[${testCaseName}] err:`+JSON.stringify(err));
}
done()
}
let innerEvent = {
eventId:eventId,
priority:events_emitter.EventPriority.LOW
}
events_emitter.on(innerEvent,callBack)
}catch(err){
console.info(`[${testCaseName}] err:`+JSON.stringify(err));
}
}
static commitKey(emitKey){
try {
let backData = {
data: {
"ACTION": emitKey
}
}
let backEvent = {
eventId:10,
priority:events_emitter.EventPriority.LOW
}
console.info("start send emitKey");
events_emitter.emit(backEvent, backData);
} catch (err) {
console.info("emit emitKey err: " + JSON.stringify(err));
}
}
static registerLargerEvent(testCaseName,eventId,done){
console.info(`[${testCaseName}] START`);
try{
let callBack=(backData)=>{
try{
console.info(`${testCaseName} get result is:`+JSON.stringify(backData));
expect(backData.data.actualValue).assertLarger(backData.data.expectedValue);
console.info(`[${testCaseName}] END`);
}catch(err){
console.info(`[${testCaseName}] err:`+JSON.stringify(err));
}
done()
}
let innerEvent = {
eventId:eventId,
priority:events_emitter.EventPriority.LOW
}
events_emitter.on(innerEvent,callBack)
}catch(err){
console.info(`[${testCaseName}] err:`+JSON.stringify(err));
}
}
}
\ No newline at end of file
/*
* Copyright (c) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @ts-nocheck
import { describe, beforeEach, afterEach, it, expect } from "@ohos/hypium";
import events_emitter from '@ohos.events.emitter';
import Utils from './Utils.ets';
let emitKey = "emitOnLoadIntercept";
export default function webJsunit() {
describe('ActsAceWebDevTest', function () {
beforeEach(async function (done) {
await Utils.sleep(2000);
console.info("web beforeEach start");
done();
})
afterEach(async function (done) {
console.info("web afterEach start:"+emitKey);
try {
let backData = {
data: {
"ACTION": emitKey
}
}
let backEvent = {
eventId:10,
priority:events_emitter.EventPriority.LOW
}
console.info("start send emitKey");
events_emitter.emit(backEvent, backData);
} catch (err) {
console.info("emit emitKey err: " + JSON.stringify(err));
}
await Utils.sleep(2000);
done();
})
/*
*tc.number SUB_ACE_BASIC_ETS_API_002
*tc.name OnLoadIntercept *tc.desic Injects the JavaScript object into window and invoke the function in window
*/
it('OnLoadIntercept',0,async function(done){
emitKey="emitOnLoadIntercept";
Utils.registerEvent("OnLoadIntercept","https://www.baidu.com/",2,done);
sendEventByKey('webcomponent',10,'');
})
})
}
/*
* Copyright (c) 2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @ts-nocheck
import { describe, beforeEach, afterEach, it, expect, beforeAll} from "@ohos/hypium";
import events_emitter from '@ohos.events.emitter';
import router from '@system.router';
import Utils from './Utils.ets';
let emitKey = "emitGetCertificate";
export default function webTwoJsunit() {
describe('overviewTest', function () {
beforeAll(async function (done) {
let options = {
uri: 'MainAbility/pages/webTwo',
}
try {
router.clear();
let pages = router.getState();
console.info("get webTwo state success " + JSON.stringify(pages));
if (!("webTwo" == pages.name)) {
console.info("get webTwo state success " + JSON.stringify(pages.name));
let result = await router.push(options);
await Utils.sleep(2000);
console.info("push webTwo page success " + JSON.stringify(result));
}
} catch (err) {
console.error("push webTwo page error: " + err);
}
done()
});
beforeEach(async function (done) {
await Utils.sleep(2000);
console.info("web beforeEach start");
done();
})
afterEach(async function (done) {
console.info("web afterEach start:"+emitKey);
try {
let backData = {
data: {
"ACTION": emitKey
}
}
let backEvent = {
eventId:10,
priority:events_emitter.EventPriority.LOW
}
console.info("start send emitKey");
events_emitter.emit(backEvent, backData);
} catch (err) {
console.info("emit emitKey err: " + JSON.stringify(err));
}
await Utils.sleep(2000);
done();
})
/*
*tc.number SUB_ACE_BASIC_ETS_API_001
*tc.name GetCertificate
*tc.desic Sets allow the Web access overview mode
*/
it('GetCertificate',0,async function(done){
emitKey="emitOverviewModeAccessFalse";
Utils.registerEvent("GetCertificate","getCertificate success: len = 0",4,done);
sendEventByKey('webcomponenttwo',10,'');
})
})
}
\ No newline at end of file
{
"module": {
"name": "phone",
"type": "entry",
"srcEntrance": "./ets/Application/AbilityStage.ts",
"description": "$string:phone_entry_dsc",
"mainElement": "MainAbility",
"deviceTypes": [
"tablet",
"default",
"phone"
],
"deliveryWithInstall": true,
"installationFree": false,
"uiSyntax": "ets",
"pages": "$profile:main_pages",
"metadata": [
{
"name": "ArkTSPartialUpdate",
"value": "false"
}
],
"abilities": [{
"name": "com.example.myapplication.MainAbility",
"srcEntrance": "./ets/MainAbility/MainAbility.ts",
"description": "$string:phone_entry_main",
"icon": "$media:icon",
"label": "$string:entry_label",
"visible": true,
"orientation": "portrait",
"skills": [{
"actions": [
"action.system.home"
],
"entities": [
"entity.system.home"
]
}]
}],
"requestPermissions": [
{
"name": "ohos.permission.LOCATION"
},
{
"name": "ohos.permission.INTERNET"
}
]
}
}
\ No newline at end of file
{
"string": [
{
"name": "phone_entry_dsc",
"value": "i am an entry for phone"
},
{
"name": "phone_entry_main",
"value": "the phone entry ability"
},
{
"name": "entry_label",
"value": "ActsContextTest"
},
{
"name": "form_description",
"value": "my form"
},
{
"name": "serviceability_description",
"value": "my whether"
},
{
"name": "description_application",
"value": "demo for test"
},
{
"name": "app_name",
"value": "Demo"
}
]
}
{
"src": [
"MainAbility/pages/web",
"MainAbility/pages/webTwo"
]
}
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>music</title>
</head>
<body>
<audio src="../base/media/gequ.mp3" loop="loop" controls="controls" autoplay="autoplay"></audio>
</body>
</html>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册