最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Flutter应用进行自动化测试

    正文概述 掘金(Tecode)   2021-08-13   706

    前言

    开发过程中,我们都会有一个很重要的环节,那就是测试。Flutter开发也一样,我们当我们完成了应用的开发之后,需要对我们的软件进行测试。市面上也有很多可以用于测试的一些自动化的软件。在这里介绍一下flutter自带的测试,我们可以通过这个插件,对我们的整个应用进行自动化的测试。

    运行环境

    [√] Flutter (Channel stable, 2.2.3, on Microsoft Windows [Version 10.0.19042.1110], locale zh-CN)
        • Flutter version 2.2.3 at D:\flutter
        • Framework revision f4abaa0735 (6 weeks ago), 2021-07-01 12:46:11 -0700
        • Engine revision 241c87ad80
        • Dart version 2.13.4
        • Pub download mirror https://pub.flutter-io.cn
        • Flutter download mirror https://storage.flutter-io.cn
    

    安装依赖

    首先,我们需要安装flutter_driver插件。安装完成以后,我们在项目中新建一个文件夹(我的文件名是test_driver自己定义文件夹名字)。这个文件夹就用于我们的测试代码的编写。

    dev_dependencies:
      flutter_driver:
        sdk: flutter
      test: any
    

    代码编写

    我要写的一个测试代码,就是我要通过代码去找到我需要的组件,比如按钮,我需要去点击它。滑动列表我需要往下划动到第多少页。然后我再往回划,切换到下一个页面,如此循环的。按照我们人类思维的流程,把整个项目全部模拟操作。

    app.dart测试入口文件

    import 'package:flutter_driver/driver_extension.dart';
    import 'package:dynamic_theme/main.dart' as app;
    
    void main() {
      // This line enables the extension.
      enableFlutterDriverExtension();
    
      // Call the `main()` function of the app, or call `runApp` with
      // any widget you are interested in testing.
      app.main();
    }
    

    app_test.dart执行需要测试模块

    我写了entrance的文件模块,里面包含了页面点击滚动的模拟事件,如果需要全模块的测试可以在main函数加多个group需要测试某个功能也可以按需测试。

    import 'package:test/test.dart';
    
    import 'entrance.dart';
    
    void main() {
      group('切换导航滑动页面', entrance);
    }
    

    entrance.dart模拟测试脚本

    import 'package:flutter_driver/flutter_driver.dart';
    import 'package:test/test.dart';
    
    void entrance() {
      late FlutterDriver driver;
      // Connect to the Flutter driver before running any tests.
      setUpAll(() async {
        driver = await FlutterDriver.connect();
      });
      // Close the connection to the driver after the tests have completed.
      tearDownAll(() async {
        await driver.close();
      });
    // test('starts at 0', () async {
    // Use the `driver.getText` method to verify the counter starts at 0.
    // expect(await driver.getText(counterTextFinder), '0');
    //    });
    
      test('切换页面', () async {
        await Future.delayed(Duration(seconds: 2));
        await driver.tap(find.byValueKey('tab_3'));
        await Future.delayed(Duration(seconds: 5));
        await driver.tap(find.byValueKey('tab_2'));
        await Future.delayed(Duration(seconds: 5));
        await driver.tap(find.byValueKey('tab_1'));
        await Future.delayed(Duration(seconds: 5));
        await driver.tap(find.byValueKey('tab_0'));
      });
    
      test('滑动页面到底部', () async {
        await driver.runUnsynchronized(() async {
          final listFinder = find.byValueKey('message_list');
          final itemFinder = find.byValueKey('item_78');
          await driver.scrollUntilVisible(
            listFinder,
            itemFinder,
            dyScroll: -300.0,
          );
        });
      });
    
      test('滑动页面到顶部', () async {
        await driver.runUnsynchronized(() async {
          final listFinder = find.byValueKey('message_list');
          final itemFinder = find.byValueKey('item_1');
          await driver.scrollUntilVisible(
            listFinder,
            itemFinder,
            dyScroll: 300.0,
          );
        });
      });
    
      test('跳转页面', () async {
        // First, tap the button.
        await driver.tap(find.byValueKey('jump_list'));
    
        // Then, verify the counter text is incremented by 1.
        expect(await driver.getText(find.byValueKey('title')), 'NewList-路由传参');
      });
    
      test('返回页面', () async {
        await Future.delayed(Duration(seconds: 5));
        final buttonFinder = find.byValueKey('back');
        // First, tap the button.
        await driver.tap(buttonFinder);
    
        // Then, verify the counter text is incremented by 1.
        // expect(await driver.getText(counterTextFinder), '1');
      });
    }
    

    怎样找到我们需要的组件?

    测试脚本使用find.byValueKey('key的名称')来找到我们需要的组件,找到以后我们可以进行点击、滚动、双击等模拟操作。这里我使用的是find.byValueKey方法,下面介绍它的使用。

    找到滚动列表,滚动某个位置

    首先我们要找到我们需要滑动的列表,我find.byValueKey这个方法去找到滑动组件,在开始写业务代码的时候我已经加了一个key名称是message_listkey不要重复避免查找组件出现问题。

    业务代码

    EasyRefresh.custom(
      key: Key('message_list'),
      enableControlFinishRefresh: true,
      ...
    

    测试代码例子

    driver.scrollUntilVisible模拟滑动,itemFinder参数表示滑动到key:item_78这个元素的时候就不滑动了,接下来每次滑动300像素。

    test('滑动页面到底部', () async {
      await driver.runUnsynchronized(() async {
        final listFinder = find.byValueKey('message_list');
        final itemFinder = find.byValueKey('item_78');
        await driver.scrollUntilVisible(
          listFinder,
          itemFinder,
          dyScroll: -300.0,
        );
      });
    });
    

    driver支持的方法

    -   [checkHealth](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/checkHealth.html)(​{[Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​[Health](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/Health-class.html)>
    
    -   Checks the status of the Flutter Driver extension.
    
    -   [clearTimeline](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/clearTimeline.html)(​{[Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout: _kUnusuallyLongTimeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​void>
    
    -   Clears all timeline events recorded up until now. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/clearTimeline.html)
    
    -   [close](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/close.html)(​) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​void>
    
    -   Closes the underlying connection to the VM service. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/close.html)
    
    -   [enterText](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/enterText.html)(​[String](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/String-class.html) text, { [Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​void>
    
    -   Enters `text` into the currently focused text input, such as the [EditableText](https://docs-flutter-io.firebaseapp.com/flutter/widgets/EditableText-class.html) widget. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/enterText.html)
    
    -   [forceGC](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/forceGC.html)(​) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​void>
    
    -   Force a garbage collection run in the VM.
    
    -   [getRenderTree](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/getRenderTree.html)(​{[Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​[RenderTree](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/RenderTree-class.html)>
    
    -   Returns a dump of the render tree.
    
    -   [getSemanticsId](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/getSemanticsId.html)(​[SerializableFinder](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/SerializableFinder-class.html) finder, { [Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​[int](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/int-class.html)>
    
    -   Retrieves the semantics node id for the object returned by `finder`, or the nearest ancestor with a semantics node. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/getSemanticsId.html)
    
    -   [getText](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/getText.html)(​[SerializableFinder](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/SerializableFinder-class.html) finder, { [Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​[String](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/String-class.html)>
    
    -   Returns the text in the `Text` widget located by `finder`.
    
    -   [getVmFlags](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/getVmFlags.html)(​) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​[List](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/List-class.html)<​[Map](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Map-class.html)<​[String](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/String-class.html), dynamic>>>
    
    -   Returns the Flags set in the Dart VM as JSON. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/getVmFlags.html)
    
    -   [requestData](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/requestData.html)(​[String](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/String-class.html) message, { [Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​[String](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/String-class.html)>
    
    -   Sends a string and returns a string. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/requestData.html)
    
    -   [runUnsynchronized](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/runUnsynchronized.html)<​T>(​[Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​T> action(), { [Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​T>
    
    -   `action` will be executed with the frame sync mechanism disabled. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/runUnsynchronized.html)
    
    -   [screenshot](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/screenshot.html)(​) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​[List](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/List-class.html)<​[int](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/int-class.html)>>
    
    -   Take a screenshot. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/screenshot.html)
    
    -   [scroll](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/scroll.html)(​[SerializableFinder](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/SerializableFinder-class.html) finder, [double](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/double-class.html) dx, [double](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/double-class.html) dy, [Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) duration, { [int](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/int-class.html) frequency: 60, [Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​void>
    
    -   Tell the driver to perform a scrolling action. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/scroll.html)
    
    -   [scrollIntoView](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/scrollIntoView.html)(​[SerializableFinder](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/SerializableFinder-class.html) finder, { [double](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/double-class.html) alignment: 0.0, [Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​void>
    
    -   Scrolls the Scrollable ancestor of the widget located by `finder` until the widget is completely visible. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/scrollIntoView.html)
    
    -   [scrollUntilVisible](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/scrollUntilVisible.html)(​[SerializableFinder](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/SerializableFinder-class.html) scrollable, [SerializableFinder](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/SerializableFinder-class.html) item, { [double](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/double-class.html) alignment: 0.0, [double](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/double-class.html) dxScroll: 0.0, [double](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/double-class.html) dyScroll: 0.0, [Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​void>
    
    -   Repeatedly [scroll](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/scroll.html) the widget located by `scrollable` by `dxScroll` and `dyScroll` until `item` is visible, and then use [scrollIntoView](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/scrollIntoView.html) to ensure the item's final position matches `alignment`. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/scrollUntilVisible.html)
    
    -   [setSemantics](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/setSemantics.html)(​[bool](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/bool-class.html) enabled, { [Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​[bool](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/bool-class.html)>
    
    -   Turns semantics on or off in the Flutter app under test. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/setSemantics.html)
    
    -   [setTextEntryEmulation](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/setTextEntryEmulation.html)(​{[bool](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/bool-class.html) enabled, [Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​void>
    
    -   Configures text entry emulation. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/setTextEntryEmulation.html)
    
    -   [startTracing](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/startTracing.html)(​{[List](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/List-class.html)<​[TimelineStream](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/TimelineStream-class.html)> streams: _defaultStreams, [Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout: _kUnusuallyLongTimeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​void>
    
    -   Starts recording performance traces. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/startTracing.html)
    
    -   [stopTracingAndDownloadTimeline](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/stopTracingAndDownloadTimeline.html)(​{[Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout: _kUnusuallyLongTimeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​[Timeline](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/Timeline-class.html)>
    
    -   Stops recording performance traces and downloads the timeline. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/stopTracingAndDownloadTimeline.html)
    
    -   [tap](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/tap.html)(​[SerializableFinder](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/SerializableFinder-class.html) finder, { [Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​void>
    
    -   Taps at the center of the widget located by `finder`.
    
    -   [traceAction](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/traceAction.html)(​[Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html) action(), { [List](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/List-class.html)<​[TimelineStream](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/TimelineStream-class.html)> streams: _defaultStreams, [bool](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/bool-class.html) retainPriorEvents: false }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​[Timeline](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/Timeline-class.html)>
    
    -   Runs `action` and outputs a performance trace for it. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/traceAction.html)
    
    -   [waitFor](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/waitFor.html)(​[SerializableFinder](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/SerializableFinder-class.html) finder, { [Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​void>
    
    -   Waits until `finder` locates the target.
    
    -   [waitForAbsent](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/waitForAbsent.html)(​[SerializableFinder](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/SerializableFinder-class.html) finder, { [Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​void>
    
    -   Waits until `finder` can no longer locate the target.
    
    -   [waitUntilNoTransientCallbacks](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/waitUntilNoTransientCallbacks.html)(​{[Duration](https://docs-flutter-io.firebaseapp.com/flutter/dart-core/Duration-class.html) timeout }) → [Future](https://docs-flutter-io.firebaseapp.com/flutter/dart-async/Future-class.html)<​void>
    
    -   Waits until there are no more transient callbacks in the queue. [[...]](https://docs-flutter-io.firebaseapp.com/flutter/flutter_driver/FlutterDriver/waitUntilNoTransientCallbacks.html)
    

    find方法支持的查找方式find.text find.byValueKey find.bySemanticsLabel find.pageBack find.byType

    SerializableFinder text(String text) => ByText(text);
    
    /// Finds widgets by [key]. Only [String] and [int] values can be used.
    SerializableFinder byValueKey(dynamic key) => ByValueKey(key);
    
    /// Finds widgets with a tooltip with the given [message].
    SerializableFinder byTooltip(String message) => ByTooltipMessage(message);
    
    /// Finds widgets with the given semantics [label].
    SerializableFinder bySemanticsLabel(Pattern label) => BySemanticsLabel(label);
    
    /// Finds widgets whose class name matches the given string.
    SerializableFinder byType(String type) => ByType(type);
    
    /// Finds the back button on a Material or Cupertino page's scaffold.
    SerializableFinder pageBack() => const PageBack();
    

    run flutter drive --target=test_driver/app.dart

    现在我们已经写好了自动化测试脚本run flutter drive --target=test_driver/app.dart命令,会在控制台上面看到很多输出的信息,说明已经开始自动化测试了,最后我们会看到控制台输出了All tests passed!的说明已经测试成功了。

    测试模拟

    Flutter应用进行自动化测试

    起源地下载网 » Flutter应用进行自动化测试

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元