คำสั่ง Query Cypress Extra สำหรับ v12+
เพิ่มแพ็คเกจนี้เป็นการพึ่งพา dev:
$ npm i -D cypress-map
# or using Yarn
$ yarn add -D cypress-map
รวมแพ็คเกจนี้ไว้ในข้อมูลจำเพาะหรือไฟล์สนับสนุนของคุณเพื่อใช้คำสั่งแบบสอบถามที่กำหนดเองทั้งหมด
import 'cypress-map'
ทางเลือก: นำเข้าเฉพาะคำสั่งแบบสอบถามที่คุณต้องการ:
import 'cypress-map/commands/map'
import 'cypress-map/commands/tap'
// and so on, see the /commands folder
const double = ( n ) => n * 2
cy . wrap ( 100 ) . apply ( double ) . should ( 'equal' , 200 )
มันทำงานเหมือน cy.then
แต่ cy.apply(fn)
เป็นคำสั่งแบบสอบถาม ฟังก์ชั่น fn
ควรเป็นแบบซิงโครนัส, ฟังก์ชั่นบริสุทธิ์ที่ใช้เฉพาะเรื่องอาร์กิวเมนต์และส่งคืนค่าใหม่ฟังก์ชันการเรียกกลับฟังก์ชัน fn
ไม่สามารถใช้คำสั่ง Cypress ใด ๆ cy
คุณสามารถผ่านอาร์กิวเมนต์ ซ้าย เพิ่มเติมไปยังฟังก์ชั่นการโทรกลับ จากนั้นมันจะทำให้เรื่องเป็น อาร์กิวเมนต์สุดท้าย ก่อนที่จะเรียกฟังก์ชั่น:
cy . wrap ( 8 ) . apply ( Cypress . _ . subtract , 4 ) . should ( 'equal' , - 4 )
หากไม่มีข้อโต้แย้ง cy.applyRight
ทำงานเหมือนกับ cy.apply
หากคุณผ่านข้อโต้แย้งแล้วเรื่องบวกอาร์กิวเมนต์จะกลายเป็นอาร์กิวเมนต์ของการโทรกลับ ตัวแบบอยู่ที่ตำแหน่ง ด้านซ้าย (แรก)
cy . wrap ( 8 ) . applyRight ( Cypress . _ . subtract , 4 ) . should ( 'equal' , 4 )
// same as
cy . wrap ( 8 )
. apply ( ( subject ) => Cypress . _ . subtract ( subject , 4 ) )
. should ( 'equal' , 4 )
บางครั้งคุณมีการโทรกลับและคุณรู้ว่าอาร์กิวเมนต์แรกและเพียงแค่ต้องวางเรื่องที่ตำแหน่งสุดท้าย นี่คือที่ที่คุณสามารถใช้อาร์กิวเมนต์ที่รู้จักบางส่วนกับการโทรกลับที่กำหนด
// the Cypress._.add takes to arguments (a, b)
// we know the first argument a = 5
// so we partially apply it and wait for the subject = b argument
cy . wrap ( 100 ) . partial ( Cypress . _ . add , 5 ) . should ( 'equal' , 105 )
// same as
cy . wrap ( 100 )
. apply ( ( subject ) => Cypress . _ . add ( 5 , subject ) )
. should ( 'equal' , 105 )
หากเรื่องปัจจุบันเป็นอาร์เรย์หรือวัตถุ jQuery คุณสามารถใช้การโทรกลับที่กำหนดกับอาร์กิวเมนต์กับรายการหรือองค์ประกอบ แรก เรื่องปัจจุบันจะเป็นอาร์กิวเมนต์ สุดท้าย
// cy.applyToFirst(callback, ...args)
cy . wrap ( Cypress . $ ( '<div>100</div><div>200</div>' ) )
. applyToFirst ( ( base , el ) => parseInt ( el . innerText , base ) , 10 )
. should ( 'equal' , 100 )
หากเรื่องปัจจุบันเป็นอาร์เรย์หรือวัตถุ jQuery คุณสามารถใช้การโทรกลับที่กำหนดกับอาร์กิวเมนต์กับรายการหรือองค์ประกอบ แรก เรื่องปัจจุบันจะเป็นอาร์กิวเมนต์ แรก
// cy.applyToFirstRight(callback, ...args)
cy . wrap ( Cypress . $ ( '<div>100</div><div>200</div>' ) )
. applyToFirstRight ( ( el , base ) => parseInt ( el . innerText , base ) , 10 )
. should ( 'equal' , 100 )
เรามักจะต้องเรียกวิธีการในองค์ประกอบ / รายการแรกในเรื่องปัจจุบัน
cy . get ( selector ) . invokeFirst ( 'getBoundingClientRect' )
// compute the vertical center for example
แปลงวัตถุทุกชิ้นในคอลเลกชันที่กำหนดโดยเรียกใช้ผ่านฟังก์ชั่นการโทรกลับที่กำหนด ยังสามารถแมปแต่ละวัตถุกับคุณสมบัติของมัน วัตถุอาจเป็นอาร์เรย์หรือวัตถุ jQuery
// map elements by invoking a function
cy . wrap ( [ '10' , '20' , '30' ] ) . map ( Number ) // [10, 20, 30]
// map elements by a property
cy . get ( '.matching' )
. map ( 'innerText' )
. should ( 'deep.equal' , [ 'first' , 'third' , 'fourth' ] )
คุณสามารถแมปคุณสมบัติของวัตถุได้โดยการแสดงรายการการโทรกลับ ตัวอย่างเช่นลองแปลงคุณสมบัติ age
จากสตริงเป็นตัวเลข
cy . wrap ( {
age : '42' ,
lucky : true ,
} )
. map ( {
age : Number ,
} )
. should ( 'deep.equal' , {
age : 42 ,
lucky : true ,
} )
คุณสามารถหลีกเลี่ยงการแปลงใด ๆ เพื่อเลือกรายการคุณสมบัติจากวัตถุ
const person = {
name : 'Joe' ,
age : 21 ,
occupation : 'student' ,
}
cy . wrap ( person ) . map ( [ 'name' , 'age' ] ) . should ( 'deep.equal' , {
name : 'Joe' ,
age : 21 ,
} )
คุณสามารถแยกเส้นทางซ้อนกันได้โดยใช้ "." ในเส้นทางทรัพย์สินของคุณ
cy . wrap ( people )
. map ( 'name.first' )
. should ( 'deep.equal' , [ 'Joe' , 'Anna' ] )
// equivalent to
cy . wrap ( people )
. map ( 'name' )
. map ( 'first' )
. should ( 'deep.equal' , [ 'Joe' , 'Anna' ] )
cy . get ( '#items li' )
. find ( '.price' )
. map ( 'innerText' )
. mapInvoke ( 'replace' , '$' , '' )
. mapInvoke ( 'trim' )
cy . get ( '#items li' )
. find ( '.price' )
. map ( 'innerText' )
. mapInvoke ( 'replace' , '$' , '' )
. map ( parseFloat )
. reduce ( ( max , n ) => ( n > max ? n : max ) )
// yields the highest price
คุณสามารถให้ค่าสะสมเริ่มต้น
cy . wrap ( [ 1 , 2 , 3 ] )
. reduce ( ( sum , n ) => sum + n , 10 )
. should ( 'equal' , 16 )
ดูลด cy.js
cy . get ( '#items li' )
. find ( '.price' )
. map ( 'innerText' )
. tap ( ) // console.log by default
. mapInvoke ( 'replace' , '$' , '' )
. mapInvoke ( 'trim' )
// console.info with extra label
. tap ( console . info , 'trimmed strings' )
ข้อสังเกต: หากมีการให้ป้ายกำกับฟังก์ชันการโทรกลับจะเรียกด้วยฉลากและเรื่อง
แบบสอบถามที่สามารถลองใหม่ได้ที่เรียกฟังก์ชันตัวสร้างที่กำหนดโดยใช้คำหลัก new
และหัวข้อปัจจุบันเป็นอาร์กิวเมนต์
cy . wrap ( 'Jan 1, 2019' )
// same as "new Date('Jan 1, 2019')"
. make ( Date )
. invoke ( 'getFullYear' )
. should ( 'equal' , 2019 )
cy.log
ที่ดีกว่า: ให้ค่าค่า stringifies อย่างชาญฉลาดโดยใช้สัญกรณ์ %
และรูปแบบสตริง
cy . wrap ( 42 )
. print ( ) // "42"
// and yields the value
. should ( 'equal' , 42 )
// pass formatting string
cy . wrap ( 42 ) . print ( 'the answer is %d' ) // "the answer is 42"
cy . wrap ( { name : 'Joe' } ) . print ( 'person %o' ) // 'person {"name":"Joe"}'
// use {0} with dot notation, supported deep properties
// https://github.com/davidchambers/string-format
cy . wrap ( { name : 'Joe' } ) . print ( 'person name {0.name}' ) // "person name Joe"
// print the length of an array
cy . wrap ( arr ) . print ( 'array length {0.length}' ) // "array length ..."
// pass your own function to return formatted string
cy . wrap ( arr ) . print ( ( a ) => `array with ${ a . length } items` )
// if you return a non-string, it will attempt to JSON.stringify it
cy . wrap ( arr ) . print ( ( list ) => list [ 2 ] ) // JSON.stringify(arr[2])
ดู print.cy.js สำหรับตัวอย่างเพิ่มเติม
ค้นหารายการเดียวในเรื่อง สมมติว่าหัวเรื่องเป็นอาร์เรย์หรือวัตถุ jQuery ใช้วิธี Lodash _.find
// using predicate function
const isThree = n => n === 3
cy . wrap ( [ ... ] ) . findOne ( isThree ) . should ( 'equal' , 3 )
// using partial known properties of an object
cy . wrap ( [ ... ] ) . findOne ( { name : 'Anna' } ) . should ( 'have.property' , 'name' , 'Anna' )
ดู find-one.cy.js
cy . get ( '.matching' )
. map ( 'innerText' )
. primo ( )
. invoke ( 'toUpperCase' )
. should ( 'equal' , 'FIRST' )
ดู primo.cy.js
ทำงานเหมือน cy.its
สำหรับวัตถุ แต่ได้รับคุณสมบัติสำหรับวัตถุ jQuery ซึ่ง cy.its
ไม่ได้
cy . get ( '#items li.matching' )
. last ( )
. prop ( 'ariaLabel' )
. should ( 'equal' , 'four' )
ดู prop.cy.js
เปลี่ยนคุณสมบัติเดียวภายในตัวแบบโดยเรียกใช้ผ่านฟังก์ชั่นการโทรกลับที่กำหนด มีประโยชน์ในการทำประเภทการแปลงตัวอย่างเช่นลองแปลงคุณสมบัติ "อายุ" เป็นตัวเลข
cy . wrap ( { age : '20' } )
. update ( 'age' , Number )
. should ( 'deep.equal' , { age : 20 } )
ส่งคืนองค์ประกอบ DOM จากวัตถุ jQuery ที่ตำแหน่ง k
ส่งคืนรายการจากอาร์เรย์ที่ตำแหน่ง k
สำหรับดัชนีเชิงลบนับรายการจากจุดสิ้นสุด
cy . get ( '#items li' ) . at ( - 1 ) . its ( 'innerText' ) . should ( 'equal' , 'fifth' )
ดู at.cy.js
ส่งคืนรายการหรือองค์ประกอบที่สุ่มเลือกจากเรื่องปัจจุบัน
cy . get ( '#items li' ) . sample ( ) . should ( 'have.text' , 'four' )
หากคุณผ่านหมายเลขบวกจะเลือกองค์ประกอบหรือรายการหลายรายการ
// yields jQuery object with 3 random items
cy . get ( '#items li' ) . sample ( 3 ) . should ( 'have.length' , 3 )
ดู sample.cy.js
ให้องค์ประกอบที่สองจากเรื่องปัจจุบัน อาจเป็นองค์ประกอบหรือรายการอาร์เรย์
cy . get ( '#items li' ) . second ( ) . should ( 'have.text' , 'second' )
ดู second.cy.js
ให้องค์ประกอบที่สามจากเรื่องปัจจุบัน อาจเป็นองค์ประกอบหรือรายการอาร์เรย์
cy . get ( '#items li' ) . third ( ) . should ( 'have.text' , 'third' )
ดูที่สาม cy.js
บันทึกวัตถุปัจจุบันในวัตถุ Cypress.env
หมายเหตุ: วัตถุ cypress.env ถูกรีเซ็ตก่อนการรันข้อมูลจำเพาะ แต่ค่าที่เปลี่ยนแปลงจะถูกส่งผ่านจากการทดสอบเพื่อทดสอบ ดังนั้นคุณสามารถส่งผ่านค่าได้อย่างง่ายดายจากการทดสอบครั้งแรกไปยังครั้งที่สอง
it ( 'saves value in this test' , ( ) => {
cy . wrap ( 'hello, world' ) . asEnv ( 'greeting' )
} )
it ( 'saved value is available in this test' , ( ) => {
expect ( Cypress . env ( 'greeting' ) , 'greeting' ) . to . equal ( 'hello, world' )
} )
คุณต้องการทำการทดสอบขึ้นอยู่กับกันและกันหรือไม่?
แบบสอบถามหน้าโดยใช้ตัวเลือกหลายตัวและส่งคืนองค์ประกอบที่พบ ในลำดับที่ระบุ ไม่ว่าพวกเขาจะสั่งซื้อในเอกสารอย่างไร ลองใหม่หากไม่พบตัวเลือกใด ๆ
cy . getInOrder ( 'selector1' , 'selector2' , 'selector3' , ... )
// yields a single jQuery subject with
// elements for selector1
// and selector2,
// and selector3, etc
คุณยังสามารถใช้สตริงตัวเลือกอาร์เรย์เดียว
cy . getInOrder ( [ 'h1' , 'h2' , 'h3' ] )
บางครั้งคุณแค่ต้องการรอจนกว่าองค์ประกอบจะมีเสถียรภาพ ตัวอย่างเช่นหากเนื้อหาข้อความขององค์ประกอบไม่เปลี่ยนแปลงสำหรับ n มิลลิวินาทีเราสามารถพิจารณาองค์ประกอบให้มีความ text
cy . get ( '#message' ) . stable ( 'text' )
// yields the element
ประเภทที่รองรับ: text
value
(สำหรับองค์ประกอบอินพุต), css
และ element
(เปรียบเทียบการอ้างอิงองค์ประกอบ)
คุณสามารถควบคุมช่วงเวลาที่เงียบ (มิลลิวินาที) และผ่าน log
และตัวเลือก timeout
// stable for 500ms
// without logging
// with maximum retries duration of 6 seconds
cy . get ( '#message' ) . stable ( 'text' , 500 , { log : false , timeout : 6_000 } )
เมื่อตรวจสอบคุณสมบัติ CSS ให้มีเสถียรภาพให้ระบุชื่อของคุณสมบัติ:
// retries until the CSS animation finishes
// and the background color is red
cy . get ( '#message' )
. stable ( 'css' , 'background-color' , 100 )
// yields the element
. should ( 'have.css' , 'background-color' , 'rgb(255, 0, 0)' )
ดู Stable.cy.js และเสถียร -css.cy.js
เกี่ยวกับการทดลอง
ลองจนกว่าองค์ประกอบที่มีตัวเลือกที่กำหนดจะแยกออกจาก DOM
cy . contains ( 'Click to re-render' ) . click ( )
cy . detaches ( '#list' )
บางครั้งการปลดสามารถเกิดขึ้นได้อย่างถูกต้องกับการกระทำและ cy.detaches(selector)
ก็ สายเกินไป หากคุณรู้ว่าการปลดอาจเกิดขึ้นแล้วคุณต้องเตรียมตัวโดยใช้นามแฝงที่เก็บไว้ในวัตถุ Cypress.env
:
cy . get ( '#name2' ) . asEnv ( 'name' )
cy . contains ( 'Click to remove Joe' ) . click ( )
cy . detaches ( '@name' )
วัตถุ jQuery จะถูกเก็บไว้ภายใน Cypress.env
ภายใต้คุณสมบัติ name
ดู detach.cy.js
คำนวณวัตถุ/อาร์เรย์ของความแตกต่างกับวัตถุ/อาร์เรย์วัตถุปัจจุบัน
cy . wrap ( { name : 'Joe' , age : 20 } )
. difference ( { name : 'Joe' , age : 30 } )
. should ( 'deep.equal' , { age : { actual : 20 , expected : 30 } } )
คุณสามารถใช้ฟังก์ชั่นภาคแสดงแบบซิงโครนัสเพื่อตรวจสอบคุณสมบัติ
// confirm the value of the "age" property
// is larger than 15
. difference ( { name : 'Joe' , age : ( n ) => n > 15 } )
รายงานที่ขาดหายไปและคุณสมบัติพิเศษ ดู different.cy.js
หมายเหตุ: ใช้ have.length
เพื่อตรวจสอบจำนวนรายการในอาร์เรย์:
// let's check if there are 3 objects in the array
// INSTEAD OF THIS
. difference ( [ Cypress . _ . object , Cypress . _ . object , Cypress . _ . object ] )
// USE AN ASSERTION
. should ( 'have.length' , 3 )
คุณสามารถตรวจสอบแต่ละรายการในหัวข้ออาร์เรย์โดยใช้ค่า / เพรดิเคตจากวัตถุที่คาดหวัง
// list of people objects
cy . wrap ( people )
. difference ( {
name : Cypress . _ . isString ,
age : ( age ) => age > 1 && age < 100 ,
} )
. should ( 'be.empty' )
หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับคำสั่ง cy.table
ให้อ่านตารางทดสอบโพสต์บล็อก HTML โดยใช้คำสั่ง cy.table query
แยกเซลล์ทั้งหมดออกจากตารางหัวเรื่องปัจจุบัน ให้ผลตอบแทน 2 มิติของสตริง
cy . get ( 'table' ) . table ( )
คุณสามารถหั่นตารางเพื่อให้ได้เพียงแค่ภูมิภาค .table(x, y, w, h)
ตัวอย่างเช่นคุณสามารถรับ 2 โดย 2 อนุภูมิภาค
cy . get ( 'table' )
. table ( 0 , 2 , 2 , 2 )
. should ( 'deep.equal' , [
[ 'Cary' , '30' ] ,
[ 'Joe' , '28' ] ,
] )
ดูข้อมูลจำเพาะ Table.cy.js สำหรับตัวอย่างเพิ่มเติม
เคล็ดลับ: คุณสามารถรวม cy.table
กับ cy.map
, cy.mapInvoke
เพื่อรับชิ้นส่วนของตาราง ตัวอย่างเช่นส่วน 2x2 เดียวกันของตารางสามารถสกัดได้ด้วย:
cy . get ( 'table' )
. table ( )
. invoke ( 'slice' , 2 , 4 )
. mapInvoke ( 'slice' , 0 , 2 )
. should ( 'deep.equal' , [
[ 'Cary' , '30' ] ,
[ 'Joe' , '28' ] ,
] )
เคล็ดลับที่ 2: เพื่อรับเพียงแถวหัวรวม .table
และ .its
queries
cy . get ( 'table' )
. table ( 0 , 0 , 3 , 1 )
. its ( 0 )
. should ( 'deep.equal' , [ 'Name' , 'Age' , 'Date (YYYY-MM-DD)' ] )
เพื่อให้ได้แถวสุดท้ายคุณสามารถทำได้:
cy . get ( 'table' ) . table ( ) . invoke ( 'slice' , - 1 ) . its ( 0 )
เพื่อให้คอลัมน์แรกเข้าร่วมในอาร์เรย์เดียว (แทนที่จะเป็นอาร์เรย์ 1x1 อาร์เรย์)
cy . get ( 'table' )
. table ( 0 , 1 , 1 ) // skip the heading "Name" cell
// combine 1x1 arrays into one array
. invoke ( 'flatMap' , Cypress . _ . identity )
. should ( 'deep.equal' , [ 'Dave' , 'Cary' , 'Joe' , 'Anna' ] )
แบบสอบถามเพื่อแปลงวัตถุ DOM พิเศษเป็นวัตถุธรรมดา ตัวอย่างเช่นในการแปลงอินสแตนซ์ DOMStringMap
เป็นวัตถุธรรมดาที่เข้ากันได้กับการยืนยัน deep.equal
ที่เราสามารถทำได้
cy . get ( 'article' )
. should ( 'have.prop' , 'dataset' )
. toPlainObject ( )
. should ( 'deep.equal' , {
columns : '3' ,
indexNumber : '12314' ,
parent : 'cars' ,
} )
โดยค่าเริ่มต้นใช้ JSON Stringify และแยกวิเคราะห์กลับ หากคุณต้องการแปลงโดยใช้ entries
และ fromEntries
ให้เพิ่มอาร์กิวเมนต์:
cy . wrap ( new URLSearchParams ( searchParams ) ) . toPlainObject ( 'entries' )
ใน Cypress V12 cy.invoke
กลายเป็นแบบสอบถามซึ่งทำให้การทำงานด้วยวิธีการแบบอะซิงโครนัสไม่เป็นไปได้จริง cy.invokeOnce
เป็นวิธีการส่งคืนแบบเก่าในการเรียกวิธีการและให้ค่าที่ได้รับการแก้ไข
cy . wrap ( app )
// app.fetchName is an asynchronous method
// that returns a Promise
. invokeOnce ( 'fetchName' )
. should ( 'equal' , 'My App' )
ดูข้อมูลจำเพาะ Invoke-once.cy.js สำหรับตัวอย่างเพิ่มเติม
นี่คือตัวอย่างเล็ก ๆ น้อย ๆ เพื่อชี้แจงความแตกต่างระหว่าง cy.invoke
, cy.map
และ cy.mapInvoke
คำสั่งคำสั่งดู diff.cy.js
const list = [ 'apples' , 'plums' , 'bananas' ]
// cy.invoke
cy . wrap ( list )
// calls ".sort()" on the list
. invoke ( 'sort' )
. should ( 'deep.equal' , [ 'apples' , 'bananas' , 'plums' ] )
// cy.mapInvoke
cy . wrap ( list )
// calls ".toUpperCase()" on every string in the list
. mapInvoke ( 'toUpperCase' )
. should ( 'deep.equal' , [ 'APPLES' , 'PLUMS' , 'BANANAS' ] )
// cy.map
const reverse = ( s ) => s . split ( '' ) . reverse ( ) . join ( '' )
cy . wrap ( list )
// reverses each string in the list
. map ( reverse )
. should ( 'deep.equal' , [ 'selppa' , 'smulp' , 'sananab' ] )
// grabs the "length" property from each string
. map ( 'length' )
. should ( 'deep.equal' , [ 6 , 5 , 7 ] )
ฉันได้เพิ่มคำสั่งที่มีประโยชน์อื่น (ไม่ใช่แบบสอบถาม!) ลงในแพ็คเกจนี้ ช่วยให้คุณสามารถประมวลผลรายการในอาเรย์ได้ทีละหนึ่งผ่านฟังก์ชั่นซิงโครนัสอะซิงโครนัสหรือ cy
นี่เป็นเพราะวิธีแก้ปัญหาทั่วไปในการดึงรายการโดยใช้ cy.each
ตัวอย่างเช่นไม่ทำงาน:
// fetch the users from a list of ids
// DOES NOT WORK
cy . get ( ids ) . each ( id => cy . request ( '/users/' + id ) ) . then ( users => ... )
// Nope, the yielded "users" result is ... still the "ids" subject
// ✅ CORRECT SOLUTION
cy . get ( ids ) . mapChain ( id => cy . request ( '/users/' + id ) ) . then ( users => ... )
แพ็คเกจนี้มีคำจำกัดความคำสั่ง TypeScript สำหรับคำสั่งที่กำหนดเองในคำสั่งไฟล์/index.d.ts เพื่อใช้จากข้อมูลจำเพาะ JavaScript ของคุณ:
/// <reference types="cypress-map" />
หากคุณใช้ typeScript ให้รวมโมดูลนี้ไว้ในรายการประเภทของคุณ
{
"compilerOptions" : {
"types" : [ " cypress " , " cypress-map " ]
}
}
ซอร์สโค้ดอยู่ในโฟลเดอร์ SRC/คำสั่ง คำสั่ง build สร้างรหัส ES5 ที่เข้าสู่โฟลเดอร์ commands
(ไม่ควรตรวจสอบในการควบคุมซอร์สโค้ด) package.json
ในการแจกแจง NPM รวมถึง commands
บวกประเภทจากไฟล์ src/commands/index.d.ts
should(callback)
รับทันที หมายเหตุ: โมดูลนี้ไม่มีวิธี filter
เนื่องจาก Cypress API มีคำสั่งแบบสอบถาม cy.filter และ cy.invoke ที่คุณสามารถใช้เพื่อกรององค์ประกอบในวัตถุ jQuery หรือรายการในอาร์เรย์ ดูตัวอย่างในตัวกรอง. cy.js ดูองค์ประกอบตัวกรองวิดีโอและรายการที่มีการลองใหม่
ผู้แต่ง: gleb bahmutov <[email protected]> © 2022
ใบอนุญาต: MIT - ทำอะไรกับรหัส แต่อย่าโทษฉันถ้ามันไม่ทำงาน
การสนับสนุน: หากคุณพบปัญหาใด ๆ กับโมดูลนี้อีเมล / ทวีต / เปิดปัญหาบน GitHub